From 66def83e3b50cc0e06fb25e424f39b73f1ba96ef Mon Sep 17 00:00:00 2001 From: TomZ Date: Wed, 18 Oct 2023 21:28:40 +0200 Subject: [PATCH 001/735] 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 002/735] 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 003/735] 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 004/735] 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 005/735] 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 006/735] 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 007/735] 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 008/735] 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 009/735] 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 010/735] {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 011/735] 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 012/735] 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 013/735] 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 014/735] 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 015/735] 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 016/735] 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 017/735] 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 018/735] 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 019/735] 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 020/735] 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 021/735] 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 022/735] 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 023/735] 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 024/735] 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 025/735] 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 026/735] 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 027/735] 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 028/735] 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 029/735] 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 030/735] 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 031/735] 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 032/735] 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 033/735] 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 034/735] 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 035/735] 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 036/735] 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 037/735] 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 038/735] 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 039/735] 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 040/735] 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 041/735] 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 042/735] 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 043/735] 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 044/735] 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 045/735] 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 046/735] 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 047/735] 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 048/735] 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 049/735] [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 050/735] 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 051/735] 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 052/735] 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 053/735] 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 054/735] 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 055/735] 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 056/735] 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 057/735] 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 058/735] 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 059/735] 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 060/735] 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 061/735] 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 062/735] 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 063/735] 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 064/735] 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 065/735] 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 066/735] 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 067/735] 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 068/735] 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 069/735] 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 070/735] 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 071/735] 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 072/735] 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 073/735] 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 074/735] 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 075/735] 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 076/735] 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 077/735] 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 078/735] 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 079/735] 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 080/735] 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 081/735] 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 082/735] 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 083/735] 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 084/735] 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 085/735] 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 086/735] 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 087/735] 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 088/735] 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 089/735] 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 090/735] 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 091/735] 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 092/735] 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 093/735] 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 094/735] 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 095/735] 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 096/735] 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 097/735] 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 098/735] 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 099/735] 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 100/735] 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 101/735] 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 102/735] 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 103/735] 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 104/735] 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 105/735] 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 106/735] 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 107/735] 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 108/735] 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 109/735] 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 110/735] 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 111/735] 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 112/735] 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 113/735] 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 114/735] 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 115/735] 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 116/735] 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 117/735] 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 118/735] 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 119/735] 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 120/735] 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 121/735] 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 122/735] 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 123/735] 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 124/735] 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 125/735] 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 126/735] 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 127/735] 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 128/735] 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 129/735] 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 130/735] 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 131/735] 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 132/735] 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 133/735] 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 134/735] 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 135/735] 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 136/735] 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 137/735] 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 138/735] 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 139/735] 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 140/735] 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 141/735] 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 142/735] 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 143/735] 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 144/735] 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 145/735] 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 146/735] 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 147/735] 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 148/735] 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 149/735] [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 150/735] 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 151/735] 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 152/735] 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 153/735] 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 154/735] 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 155/735] 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 156/735] 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 157/735] 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 158/735] 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 159/735] 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 160/735] 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 161/735] 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 162/735] 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 163/735] 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 164/735] 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 165/735] 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 166/735] 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 167/735] 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 168/735] 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 169/735] 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 170/735] 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 171/735] 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 172/735] 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 173/735] 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 174/735] 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 175/735] 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 176/735] 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 177/735] 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 178/735] 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 179/735] 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 180/735] 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 181/735] 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 182/735] 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 183/735] 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 184/735] 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 185/735] 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 186/735] 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 187/735] 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 188/735] 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 189/735] 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 190/735] 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 191/735] 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 192/735] 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 193/735] 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 194/735] 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 195/735] 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 196/735] 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 197/735] 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 198/735] 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 199/735] 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 200/735] 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 201/735] 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 202/735] 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 203/735] 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 204/735] 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 205/735] 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 206/735] 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 207/735] 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 208/735] 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 209/735] 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 210/735] 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 211/735] 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 212/735] 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 213/735] 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 214/735] 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 215/735] 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 216/735] 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 217/735] 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 218/735] 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 219/735] 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 220/735] 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 221/735] 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 222/735] 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 223/735] 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 224/735] 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 225/735] 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 226/735] 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 227/735] 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 228/735] 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 229/735] 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 230/735] 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 231/735] 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 232/735] 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 233/735] 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 234/735] 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 235/735] 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 236/735] 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 237/735] 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 238/735] 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 239/735] 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 240/735] 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 241/735] 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 242/735] 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 243/735] 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 244/735] 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 245/735] 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 246/735] 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 247/735] 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 248/735] 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 249/735] 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 250/735] 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 251/735] 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 252/735] 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 253/735] 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 254/735] 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 255/735] 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 256/735] 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 257/735] 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 258/735] 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 259/735] 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 260/735] 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 261/735] 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 262/735] 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 263/735] 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 264/735] 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 265/735] 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 266/735] 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 267/735] 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 268/735] 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 269/735] 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 270/735] 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 271/735] 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 272/735] 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 273/735] 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 274/735] 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 275/735] 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 276/735] 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 277/735] 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 278/735] 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 279/735] 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 280/735] 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 281/735] 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 282/735] 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 283/735] 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 284/735] 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 285/735] 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 286/735] 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 287/735] 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 288/735] 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 289/735] 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 290/735] 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 291/735] 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 292/735] 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 293/735] 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 294/735] 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 295/735] 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 296/735] 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 297/735] 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 298/735] 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 299/735] 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 300/735] 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 301/735] 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 302/735] 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 303/735] 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 304/735] 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 305/735] 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 306/735] 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 307/735] 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 308/735] 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 309/735] 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 310/735] 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 311/735] 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 312/735] 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 313/735] 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 314/735] 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 315/735] 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 316/735] 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 317/735] 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 318/735] 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 319/735] 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 320/735] 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 321/735] 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 322/735] 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 323/735] 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 324/735] 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 325/735] 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 326/735] 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 327/735] 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 328/735] 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 329/735] 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 330/735] 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 331/735] [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 332/735] 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 333/735] 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 334/735] 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 335/735] 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 336/735] 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 337/735] 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 338/735] 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 339/735] 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 340/735] 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 341/735] 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 342/735] 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 343/735] 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 344/735] 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 345/735] 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 346/735] 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 347/735] 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 348/735] 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 349/735] 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 350/735] 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 351/735] 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 352/735] 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 353/735] 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 354/735] 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 355/735] 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 356/735] 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 357/735] 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 358/735] 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 359/735] 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 360/735] 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 361/735] 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 362/735] 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 363/735] 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 364/735] 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 365/735] 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 366/735] 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 367/735] 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 368/735] 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 369/735] 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 370/735] 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 371/735] 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 372/735] 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 373/735] 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 374/735] 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 375/735] 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 376/735] 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 377/735] 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 378/735] 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 379/735] 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 380/735] 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 381/735] 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 382/735] 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 383/735] 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 384/735] 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 385/735] 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 386/735] 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 387/735] 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 388/735] 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 389/735] 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 390/735] 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 391/735] 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 392/735] 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 393/735] 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 394/735] 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 395/735] 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 396/735] 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 397/735] 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 398/735] 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 399/735] 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 400/735] 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 401/735] 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 402/735] 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 403/735] 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 404/735] 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 405/735] 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 406/735] 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 407/735] 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 408/735] 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 409/735] 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 410/735] 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 411/735] 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 412/735] 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 413/735] 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 414/735] 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 415/735] 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 416/735] 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 417/735] 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 418/735] 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 419/735] 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 420/735] 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 421/735] 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 422/735] 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 423/735] 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 424/735] 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 425/735] 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 426/735] 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 427/735] 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 428/735] 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 429/735] 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 430/735] 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 431/735] 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 432/735] 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 433/735] 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 434/735] 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 435/735] 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 436/735] 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 437/735] 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 438/735] 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 439/735] 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 440/735] 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 441/735] 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 442/735] 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 443/735] 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 444/735] 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 445/735] 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 446/735] 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 447/735] 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 448/735] 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 449/735] 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 450/735] 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 451/735] 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 452/735] 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 453/735] 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 454/735] 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 455/735] 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 456/735] 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 457/735] 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 458/735] 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 459/735] 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 460/735] 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 461/735] 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 462/735] 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 463/735] 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 464/735] 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 465/735] 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 466/735] 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 467/735] [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 468/735] 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 469/735] 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 470/735] 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 471/735] 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 472/735] 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 473/735] 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 474/735] 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 475/735] 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 476/735] 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 477/735] 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 478/735] 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 479/735] 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 480/735] 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 481/735] 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 482/735] 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 483/735] 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 484/735] 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 485/735] 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 486/735] 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 487/735] 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 488/735] 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 489/735] 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 490/735] 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 491/735] 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 492/735] 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 493/735] 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 494/735] 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 495/735] 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 496/735] 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 497/735] 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 498/735] 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 499/735] 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 500/735] 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 501/735] 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 502/735] 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 503/735] 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 504/735] 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 505/735] 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 506/735] 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 507/735] 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 508/735] 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 509/735] 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 510/735] 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 511/735] 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 512/735] 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 513/735] 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 514/735] 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 515/735] 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 516/735] 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 517/735] 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 518/735] 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 519/735] 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 520/735] 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 521/735] 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 522/735] 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 523/735] 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 524/735] 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 525/735] 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 526/735] 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 527/735] 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 528/735] 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 529/735] 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 530/735] 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 531/735] 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 532/735] 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 533/735] 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 534/735] 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 535/735] 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 536/735] 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 537/735] 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 538/735] 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 539/735] 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 540/735] 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 541/735] 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 542/735] 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 543/735] 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 544/735] 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 545/735] 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 546/735] 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 547/735] 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 548/735] 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 549/735] 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 550/735] 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 551/735] 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 552/735] 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 553/735] 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 554/735] 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 555/735] 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 556/735] 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 557/735] 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 558/735] 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 559/735] 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 560/735] 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 561/735] 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 562/735] 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 563/735] 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 564/735] 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 565/735] 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 566/735] 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 567/735] 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 568/735] 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 569/735] 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 570/735] 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 571/735] 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 572/735] 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 573/735] 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 574/735] 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 575/735] 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 576/735] 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 577/735] 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 578/735] 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 579/735] 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 580/735] 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 581/735] 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 582/735] 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 583/735] 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 584/735] 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 585/735] 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 586/735] 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 587/735] 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 588/735] 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 589/735] 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 590/735] 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 591/735] 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 592/735] 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 593/735] 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 594/735] 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 595/735] 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 596/735] 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 597/735] 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 598/735] 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 599/735] 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 600/735] 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 601/735] 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 602/735] 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 603/735] 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 604/735] 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 605/735] 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 606/735] 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 607/735] 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 608/735] 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 609/735] 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 610/735] 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 611/735] 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 612/735] 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 613/735] 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 614/735] 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 615/735] 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 616/735] 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 617/735] 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 618/735] 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 619/735] 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 620/735] 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 621/735] 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 622/735] 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 623/735] 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 624/735] 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 625/735] 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 626/735] 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 627/735] 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 628/735] 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 629/735] 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 630/735] 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 631/735] 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 632/735] 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 633/735] 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 634/735] 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 635/735] 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 636/735] 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 637/735] 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 638/735] 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 639/735] 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 640/735] 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 641/735] 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 642/735] 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 643/735] 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 644/735] 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 645/735] 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 646/735] 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 647/735] 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 648/735] 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 649/735] 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 650/735] 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 651/735] 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 652/735] 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 653/735] 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 654/735] 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 655/735] 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 656/735] 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 657/735] 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 658/735] 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 659/735] 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 660/735] 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 661/735] 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 662/735] 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 663/735] 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 664/735] 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 665/735] 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 666/735] 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 667/735] 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 668/735] 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 669/735] 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 670/735] 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 671/735] 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 672/735] 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 673/735] 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 674/735] 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 675/735] 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 676/735] 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 677/735] 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 678/735] 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 679/735] 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 680/735] 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 681/735] 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 682/735] 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 683/735] 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 684/735] 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 685/735] 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 686/735] 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 687/735] 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 688/735] 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 689/735] 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 690/735] 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 691/735] 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 692/735] 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 693/735] 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 694/735] 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 695/735] 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 696/735] 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 697/735] 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 698/735] 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 699/735] 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 700/735] 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 701/735] 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 702/735] 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 703/735] 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 704/735] 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 705/735] 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 706/735] 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 707/735] 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 708/735] 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 709/735] 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 710/735] 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 711/735] 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 712/735] 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 713/735] 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 714/735] 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 715/735] 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 716/735] 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 717/735] 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 718/735] 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 719/735] 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 720/735] 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 721/735] 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 722/735] 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 723/735] 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 724/735] 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 725/735] 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 726/735] 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 727/735] 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 728/735] 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 729/735] 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 730/735] 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 731/735] 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 732/735] 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 733/735] 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 734/735] 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 735/735] 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