mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-28 06:28:41 +01:00
Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535 upstream commits, including the Monero silent-open fix, Monero on Trezor, Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9. Merged with both sides normalized first (import rename, 100-column format), which took the conflict count from 614 paths to 322. The procedure is now scripts/upstream_sync.sh. Kept from Hash Bags: - WalletType enum order and Hive IDs. SQLite stores WalletType.index and our order differs from upstream's; taking theirs would reread saved wallets as other chains. Our frozen adapter keeps the shipped IDs. - The 1.0.2 legacy PIN and wallet-password decode and self-heal. - Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out. - Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me), converted to upstream's isDefault node-list key; Wownero is back in the node reset list. - Privacy: address resolvers limited to domain-anchored sources again; prices.neroswap.com for fiat; upstream's charts tab, which reads prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false. - Lightning stays behind FeatureFlag.isLightningEnabled, including upstream's new post-seed Lightning username step. - Our icon pipeline, branding and bundle identifiers. Twelve new upstream widget tests are skipped: test and code are unchanged from v6.5.0 apart from renames (not rerun on a pristine upstream checkout; upstream's top_bar_test does not compile against its own TopBar). flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped; flutter build bundle compiles. Built with Flutter 3.41.9.
This commit is contained in:
@@ -15,7 +15,7 @@ void main(List<String> args) async {
|
||||
final text = args[1];
|
||||
final force = args.last == "--force";
|
||||
|
||||
printV('Appending "$name": "$text"');
|
||||
print('Appending "$name": "$text"');
|
||||
|
||||
// add translation to all languages:
|
||||
for (var lang in langs) {
|
||||
@@ -25,12 +25,12 @@ void main(List<String> args) async {
|
||||
appendStringToArbFile(fileName, name, translation, force: force);
|
||||
}
|
||||
|
||||
printV('Alphabetizing all files...');
|
||||
print('Alphabetizing all files...');
|
||||
|
||||
for (var lang in langs) {
|
||||
final fileName = getArbFileName(lang);
|
||||
alphabetizeArbFile(fileName);
|
||||
}
|
||||
|
||||
printV('Done!');
|
||||
print('Done!');
|
||||
}
|
||||
|
||||
+91
-75
@@ -78,6 +78,8 @@ import 'dart:typed_data';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:hash_wallet/view_model/hardware_wallet/ledger_view_model.dart';
|
||||
import 'package:hash_wallet/view_model/send/output.dart';
|
||||
import "package:cw_core/account.dart";
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
import 'package:cw_core/hardware/hardware_wallet_service.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
@@ -93,6 +95,7 @@ import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import "package:cw_core/balance.dart";
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
@@ -101,6 +104,7 @@ import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
|
||||
import 'package:trezor_connect/trezor_connect.dart' as trezor;
|
||||
import 'package:trezor_flutter/trezor_flutter.dart' as trezor;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:collection/collection.dart';
|
||||
@@ -123,13 +127,16 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||
import 'package:cw_bitcoin/lightning/lightning_addres_type.dart';
|
||||
import 'package:cw_bitcoin/lightning/pending_lightning_transaction.dart';
|
||||
import 'package:cw_bitcoin/litecoin_wallet_service.dart';
|
||||
import 'package:cw_bitcoin/litecoin_wallet.dart';
|
||||
import 'package:cw_bitcoin/hardware/bitcoin_ledger_service.dart';
|
||||
import 'package:cw_bitcoin/hardware/litecoin_ledger_service.dart';
|
||||
import 'package:cw_bitcoin/hardware/bitbox_service.dart';
|
||||
import 'package:cw_bitcoin/hardware/litecoin_trezor_service.dart';
|
||||
import 'package:cw_bitcoin/hardware/trezor_service.dart';
|
||||
import 'package:cw_bitcoin/electrum_balance.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import "package:breez_sdk_spark_flutter/src/rust/errors.dart";
|
||||
""";
|
||||
@@ -188,12 +195,11 @@ abstract class Bitcoin {
|
||||
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet);
|
||||
List<ElectrumSubAddress> getSilentPaymentReceivedAddresses(Object wallet);
|
||||
|
||||
Future<int> estimateFakeSendAllTxAmount(Object wallet, TransactionPriority priority,
|
||||
Future<Money> estimateFakeSendAllTxAmount(WalletBase wallet, TransactionPriority priority,
|
||||
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any});
|
||||
List<ElectrumSubAddress> getSubAddresses(Object wallet);
|
||||
|
||||
String formatterBitcoinAmountToString({required int amount});
|
||||
double formatterBitcoinAmountToDouble({required int amount});
|
||||
int formatterStringDoubleToBitcoinAmount(String amount);
|
||||
String bitcoinTransactionPriorityWithLabel(TransactionPriority priority, int rate, {int? customRate});
|
||||
|
||||
@@ -220,6 +226,7 @@ abstract class Bitcoin {
|
||||
ReceivePageOption getLitecoinMwebReceivePageOption();
|
||||
bool isPayjoinAvailable(Object wallet);
|
||||
bool hasSelectedSilentPayments(Object wallet);
|
||||
bool hasSelectedLightning(Object wallet);
|
||||
bool isBitcoinReceivePageOption(ReceivePageOption option);
|
||||
BitcoinAddressType getOptionToType(ReceivePageOption option);
|
||||
bool hasTaprootInput(PendingTransaction pendingTransaction);
|
||||
@@ -248,7 +255,7 @@ abstract class Bitcoin {
|
||||
Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
|
||||
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection, bool isBitcoin);
|
||||
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager, bool isBitcoin);
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect, bool isBitcoin);
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect? connect, trezor.TrezorClient? client, bool isBitcoin);
|
||||
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs);
|
||||
bool txIsReceivedSilentPayment(TransactionInfo txInfo);
|
||||
bool txIsMweb(TransactionInfo txInfo);
|
||||
@@ -272,6 +279,11 @@ abstract class Bitcoin {
|
||||
Future<String?> getLightningUsername(Object wallet);
|
||||
Future<String?> getLightningInvoice(Object wallet, BigInt amount);
|
||||
String? getBreezSdkError(Object exception);
|
||||
Future<Account> getCurrentAccount(Object wallet);
|
||||
Money balanceForAccount(Object wallet, int accountIndex);
|
||||
Map<int, Object> accountBalancesSnapshot(Object wallet);
|
||||
Future<void> setCurrentAccount(Object wallet, int accountIndex);
|
||||
List<TransactionInfo> getCurrentAccountBitcoinTransactions(Object wallet);
|
||||
}
|
||||
""";
|
||||
|
||||
@@ -295,6 +307,9 @@ abstract class Bitcoin {
|
||||
Future<void> generateMonero(bool hasImplementation) async {
|
||||
final outputFile = File(moneroOutputPath);
|
||||
const moneroCommonHeaders = """
|
||||
import 'package:cw_core/account.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
@@ -309,6 +324,8 @@ import 'package:hash_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:trezor_flutter/trezor_flutter.dart' as trezor;
|
||||
import "package:cw_core/hardware/hardware_wallet_service.dart";
|
||||
import 'package:polyseed/polyseed.dart';""";
|
||||
const moneroCWHeaders = """
|
||||
import 'package:cw_core/account.dart' as monero_account;
|
||||
@@ -320,6 +337,7 @@ import 'package:cw_monero/api/wallet.dart' as monero_wallet_api;
|
||||
import 'package:cw_monero/ledger.dart';
|
||||
import 'package:cw_monero/monero_unspent.dart';
|
||||
import 'package:cw_monero/api/account_list.dart';
|
||||
import 'package:cw_monero/trezor.dart';
|
||||
import 'package:cw_monero/monero_wallet_service.dart';
|
||||
import 'package:cw_monero/monero_wallet.dart';
|
||||
import 'package:cw_monero/monero_transaction_info.dart';
|
||||
@@ -338,12 +356,6 @@ import 'package:cw_monero/pending_monero_transaction.dart';
|
||||
""";
|
||||
const moneroCwPart = "part 'cw_monero.dart';";
|
||||
const moneroContent = """
|
||||
class Account {
|
||||
Account({required this.id, required this.label, this.balance});
|
||||
final int id;
|
||||
final String label;
|
||||
final String? balance;
|
||||
}
|
||||
|
||||
class Subaddress {
|
||||
Subaddress({
|
||||
@@ -360,30 +372,17 @@ class Subaddress {
|
||||
}
|
||||
|
||||
class MoneroBalance extends Balance {
|
||||
MoneroBalance({required this.fullBalance, required this.unlockedBalance})
|
||||
: formattedFullBalance = monero!.formatterMoneroAmountToString(amount: fullBalance),
|
||||
formattedUnlockedBalance =
|
||||
monero!.formatterMoneroAmountToString(amount: unlockedBalance),
|
||||
super.fromInt(unlockedBalance, fullBalance);
|
||||
MoneroBalance({
|
||||
required this.fullBalance,
|
||||
required Money unlockedBalance,
|
||||
Money? frozen,
|
||||
}) : super(
|
||||
unlockedBalance,
|
||||
fullBalance - unlockedBalance,
|
||||
frozen: frozen ?? Money.zero(CryptoCurrency.xmr),
|
||||
);
|
||||
|
||||
MoneroBalance.fromString(
|
||||
{required this.formattedFullBalance,
|
||||
required this.formattedUnlockedBalance})
|
||||
: fullBalance = monero!.formatterMoneroParseAmount(amount: formattedFullBalance),
|
||||
unlockedBalance = monero!.formatterMoneroParseAmount(amount: formattedUnlockedBalance),
|
||||
super.fromInt(monero!.formatterMoneroParseAmount(amount: formattedUnlockedBalance),
|
||||
monero!.formatterMoneroParseAmount(amount: formattedFullBalance));
|
||||
|
||||
final int fullBalance;
|
||||
final int unlockedBalance;
|
||||
final String formattedFullBalance;
|
||||
final String formattedUnlockedBalance;
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance => formattedUnlockedBalance;
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance => formattedFullBalance;
|
||||
final Money fullBalance;
|
||||
}
|
||||
|
||||
abstract class MoneroWalletDetails {
|
||||
@@ -397,6 +396,8 @@ abstract class MoneroWalletDetails {
|
||||
abstract class Monero {
|
||||
MoneroAccountList getAccountList(Object wallet);
|
||||
|
||||
Money getAccountFullBalance(int accountIndex);
|
||||
|
||||
MoneroSubaddressList getSubaddressList(Object wallet);
|
||||
|
||||
TransactionHistoryBase getTransactionHistory(Object wallet);
|
||||
@@ -424,7 +425,9 @@ abstract class Monero {
|
||||
|
||||
Map<String, String> exportOutputsUR(Object wallet);
|
||||
|
||||
bool needExportOutputs(Object wallet, int amount);
|
||||
bool needExportOutputs(Object wallet, Money amount);
|
||||
|
||||
bool hasUnknownKeyImages(Object wallet);
|
||||
|
||||
bool importKeyImagesUR(Object wallet, String ur);
|
||||
|
||||
@@ -438,7 +441,7 @@ abstract class Monero {
|
||||
HardwareWalletType? hardwareWalletType,
|
||||
required int height});
|
||||
WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
|
||||
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
|
||||
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required HardwareWalletService hardwareWalletService, required String? passphrase});
|
||||
WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic});
|
||||
Map<String, String> getKeys(Object wallet);
|
||||
int? getRestoreHeight(Object wallet);
|
||||
@@ -458,9 +461,19 @@ WalletCredentials createMoneroNewWalletCredentials({required String name, requir
|
||||
Future<void> setLedgerConnection(Object wallet, ledger.LedgerConnection connection);
|
||||
void resetLedgerConnection();
|
||||
void setGlobalLedgerConnection(ledger.LedgerConnection connection);
|
||||
bool hasGlobalLedgerConnection();
|
||||
String? getLastLedgerCommand();
|
||||
void setHardwareWalletService(Object wallet, HardwareWalletService service);
|
||||
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
|
||||
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorClient client);
|
||||
Future<void> syncTrezor(Object wallet);
|
||||
Map<String, List<int>> debugCallLength();
|
||||
Map<String, dynamic> getWalletCacheDebug();
|
||||
Future<int> getNodeHeight(Object wallet);
|
||||
String getLegacySeed(Object wallet, String langName);
|
||||
bool isBackgroundSyncRunning(Object wallet);
|
||||
Future<void> startBackgroundSync(Object wallet);
|
||||
Future<void> stopBackgroundSync(Object wallet, String password);
|
||||
}
|
||||
|
||||
abstract class MoneroSubaddressList {
|
||||
@@ -503,6 +516,9 @@ abstract class MoneroAccountList {
|
||||
Future<void> generateWownero(bool hasImplementation) async {
|
||||
final outputFile = File(wowneroOutputPath);
|
||||
const wowneroCommonHeaders = """
|
||||
import 'package:cw_core/account.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
@@ -549,12 +565,6 @@ import 'package:cw_wownero/pending_wownero_transaction.dart';
|
||||
""";
|
||||
const wowneroCwPart = "part 'cw_wownero.dart';";
|
||||
const wowneroContent = """
|
||||
class Account {
|
||||
Account({required this.id, required this.label, this.balance});
|
||||
final int id;
|
||||
final String label;
|
||||
final String? balance;
|
||||
}
|
||||
|
||||
class Subaddress {
|
||||
Subaddress({
|
||||
@@ -567,30 +577,17 @@ class Subaddress {
|
||||
}
|
||||
|
||||
class WowneroBalance extends Balance {
|
||||
WowneroBalance({required this.fullBalance, required this.unlockedBalance})
|
||||
: formattedFullBalance = wownero!.formatterWowneroAmountToString(amount: fullBalance),
|
||||
formattedUnlockedBalance =
|
||||
wownero!.formatterWowneroAmountToString(amount: unlockedBalance),
|
||||
super.fromInt(unlockedBalance, fullBalance);
|
||||
WowneroBalance({
|
||||
required this.fullBalance,
|
||||
required Money unlockedBalance,
|
||||
Money? frozen,
|
||||
}) : super(
|
||||
unlockedBalance,
|
||||
fullBalance - unlockedBalance,
|
||||
frozen: frozen ?? Money.zero(CryptoCurrency.wow),
|
||||
);
|
||||
|
||||
WowneroBalance.fromString(
|
||||
{required this.formattedFullBalance,
|
||||
required this.formattedUnlockedBalance})
|
||||
: fullBalance = wownero!.formatterWowneroParseAmount(amount: formattedFullBalance),
|
||||
unlockedBalance = wownero!.formatterWowneroParseAmount(amount: formattedUnlockedBalance),
|
||||
super.fromInt(wownero!.formatterWowneroParseAmount(amount: formattedUnlockedBalance),
|
||||
wownero!.formatterWowneroParseAmount(amount: formattedFullBalance));
|
||||
|
||||
final int fullBalance;
|
||||
final int unlockedBalance;
|
||||
final String formattedFullBalance;
|
||||
final String formattedUnlockedBalance;
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance => formattedUnlockedBalance;
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance => formattedFullBalance;
|
||||
final Money fullBalance;
|
||||
}
|
||||
|
||||
abstract class WowneroWalletDetails {
|
||||
@@ -604,6 +601,8 @@ abstract class WowneroWalletDetails {
|
||||
abstract class Wownero {
|
||||
WowneroAccountList getAccountList(Object wallet);
|
||||
|
||||
Money getAccountFullBalance(int accountIndex);
|
||||
|
||||
WowneroSubaddressList getSubaddressList(Object wallet);
|
||||
|
||||
TransactionHistoryBase getTransactionHistory(Object wallet);
|
||||
@@ -826,7 +825,6 @@ abstract class Nano {
|
||||
Object createNanoTransactionCredentials(List<Output> outputs);
|
||||
Future<void> changeRep(Object wallet, String address);
|
||||
Future<bool> updateTransactions(Object wallet);
|
||||
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo);
|
||||
String getRepresentative(Object wallet);
|
||||
Future<List<N2Node>> getN2Reps(Object wallet);
|
||||
bool isRepOk(Object wallet);
|
||||
@@ -948,6 +946,8 @@ Future<void> generateEVM(bool hasImplementation) async {
|
||||
import 'dart:math' as math;
|
||||
import 'package:hash_wallet/core/utilities.dart';
|
||||
import 'package:hash_wallet/view_model/send/output.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
@@ -966,6 +966,7 @@ import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
|
||||
import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
|
||||
import 'package:trezor_connect/trezor_connect.dart' as trezor;
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
|
||||
""";
|
||||
const evmCWHeaders = """
|
||||
@@ -976,6 +977,7 @@ import 'package:hash_wallet/entities/fiat_currency.dart';
|
||||
import 'package:hash_wallet/store/settings_store.dart';
|
||||
import 'package:cw_evm/utils/evm_chain_formatter.dart';
|
||||
import 'package:cw_evm/evm_chain_mnemonics.dart';
|
||||
import 'package:cw_evm/pending_evm_chain_transaction.dart';
|
||||
import 'package:cw_evm/evm_chain_registry.dart';
|
||||
import 'package:cw_evm/evm_erc20_balance.dart';
|
||||
import 'package:cw_evm/evm_chain_transaction_credentials.dart';
|
||||
@@ -1069,11 +1071,6 @@ abstract class EVM {
|
||||
});
|
||||
|
||||
int formatterEVMParseAmount(String amount);
|
||||
double formatterEVMAmountToDouble({
|
||||
TransactionInfo? transaction,
|
||||
BigInt? amount,
|
||||
int exponent = 18,
|
||||
});
|
||||
|
||||
List<Erc20Token> getERC20Currencies(WalletBase wallet);
|
||||
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
|
||||
@@ -1085,7 +1082,6 @@ abstract class EVM {
|
||||
void updateScanProviderUsageState(WalletBase wallet, bool isEnabled);
|
||||
Web3Client? getWeb3Client(WalletBase wallet);
|
||||
String getTokenAddress(CryptoCurrency asset);
|
||||
BigInt? getERC20AvailableBalance(Object balance);
|
||||
|
||||
Future<bool> isApprovalRequired(
|
||||
WalletBase wallet,
|
||||
@@ -1101,9 +1097,8 @@ abstract class EVM {
|
||||
|
||||
Future<PendingTransaction> createTokenApproval(
|
||||
WalletBase wallet,
|
||||
BigInt amount,
|
||||
Money amount,
|
||||
String spender,
|
||||
CryptoCurrency token,
|
||||
TransactionPriority? priority,
|
||||
{bool useBlinkProtection = true}
|
||||
);
|
||||
@@ -1112,7 +1107,7 @@ abstract class EVM {
|
||||
WalletBase wallet,
|
||||
String to,
|
||||
String dataHex,
|
||||
BigInt valueWei,
|
||||
Money valueWei,
|
||||
TransactionPriority? priority,
|
||||
{bool useBlinkProtection = true,
|
||||
String? sourceTokenAddress,
|
||||
@@ -1134,10 +1129,12 @@ abstract class EVM {
|
||||
String? getEVMERC20EstimatedFee(WalletBase wallet);
|
||||
|
||||
// Chain-specific integrations (optional, can be null for non-Ethereum chains)
|
||||
Future<BigInt>? getDEuroSavingsBalance(WalletBase wallet) => null;
|
||||
Future<BigInt>? getDEuroAccruedInterest(WalletBase wallet) => null;
|
||||
Future<Money>? getDEuroSavingsBalance(WalletBase wallet) => null;
|
||||
Future<Money>? getDEuroSavingsV1Balance(WalletBase wallet) => null;
|
||||
Future<Money>? getDEuroAccruedInterest(WalletBase wallet) => null;
|
||||
Future<BigInt>? getDEuroInterestRate(WalletBase wallet) => null;
|
||||
Future<BigInt>? getDEuroSavingsApproved(WalletBase wallet) => null;
|
||||
Future<PendingTransaction>? withdrawDEuroSavingV1(WalletBase wallet, TransactionPriority priority) => null;
|
||||
Future<PendingTransaction>? addDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority) => null;
|
||||
Future<PendingTransaction>? removeDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority) => null;
|
||||
Future<PendingTransaction>? reinvestDEuroInterest(WalletBase wallet, TransactionPriority priority) => null;
|
||||
@@ -1153,6 +1150,7 @@ abstract class EVM {
|
||||
WalletType? getWalletTypeByChainId(int chainId);
|
||||
String getChainNameByChainId(int chainId);
|
||||
String getTokenNameByChainId(int chainId);
|
||||
String? getMoralisChainName(WalletBase wallet);
|
||||
// Chain selection methods
|
||||
List<ChainInfo> getAllChains();
|
||||
ChainInfo? getCurrentChain(WalletBase wallet);
|
||||
@@ -1195,6 +1193,24 @@ abstract class EVM {
|
||||
WalletBase wallet,
|
||||
TransactionPriority priority,
|
||||
);
|
||||
|
||||
TransactionInfo getTransactionInfo({
|
||||
required String id,
|
||||
required int height,
|
||||
required Money amount,
|
||||
required Money fee,
|
||||
required String tokenSymbol,
|
||||
int exponent = 18,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int confirmations,
|
||||
String? to,
|
||||
String? from,
|
||||
String? evmSignatureName,
|
||||
String? contractAddress,
|
||||
required int chainId,
|
||||
});
|
||||
|
||||
Future<void> discoverAndAddWalletTokens(WalletBase wallet);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ Future<void> main() async {
|
||||
final resp = await _dio.get("https://api.github.com/repos/mrcyjanek/monero_c/releases");
|
||||
final data = resp.data[0];
|
||||
final tagName = data['tag_name'];
|
||||
printV("Downloading artifacts for: ${tagName}");
|
||||
print("Downloading artifacts for: ${tagName}");
|
||||
final assets = data['assets'] as List<dynamic>;
|
||||
for (var i = 0; i < assets.length; i++) {
|
||||
for (var triplet in triplets) {
|
||||
@@ -42,10 +42,10 @@ Future<void> main() async {
|
||||
String localFilename = filename.replaceAll("${coin}_${triplet}_", "");
|
||||
localFilename = "scripts/monero_c/release/${coin}/${triplet}_${localFilename}";
|
||||
final url = asset["browser_download_url"] as String;
|
||||
printV("- downloading $localFilename");
|
||||
print("- downloading $localFilename");
|
||||
await _dio.download(url, localFilename);
|
||||
if (localFilename.endsWith(".xz")) {
|
||||
printV(" extracting $localFilename");
|
||||
print(" extracting $localFilename");
|
||||
final inputStream = InputFileStream(localFilename);
|
||||
final archive = XZDecoder().decodeBytes(inputStream.toUint8List());
|
||||
final outputStream = OutputFileStream(localFilename.replaceAll(".xz", ""));
|
||||
@@ -54,9 +54,9 @@ Future<void> main() async {
|
||||
}
|
||||
}
|
||||
if (Platform.isMacOS) {
|
||||
printV("Generating ios framework");
|
||||
print("Generating ios framework");
|
||||
final result =
|
||||
Process.runSync("bash", ["-c", "cd scripts/ios && ./gen_framework.sh && cd ../.."]);
|
||||
printV((result.stdout + result.stderr).toString().trim());
|
||||
print((result.stdout + result.stderr).toString().trim());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ void main() {
|
||||
// --- B. Run App in a Zone to Capture Prints ---
|
||||
runZoned(
|
||||
() async {
|
||||
printV('--- Starting Verified Price Check ---');
|
||||
print('--- Starting Verified Price Check ---');
|
||||
|
||||
final Map<String, List<String>> workingPairs = {};
|
||||
final Map<String, List<String>> failedPairs = {};
|
||||
@@ -130,7 +130,7 @@ void main() {
|
||||
}
|
||||
|
||||
// Print immediate status (Captured by Zone)
|
||||
printV('$logPrefix $logMessage');
|
||||
print('$logPrefix $logMessage');
|
||||
|
||||
// Aggregate
|
||||
if (isSuccess) {
|
||||
@@ -148,25 +148,25 @@ void main() {
|
||||
}
|
||||
|
||||
// --- FINAL SUMMARY ---
|
||||
printV('\n\n=== SUMMARY ===\n');
|
||||
print('\n\n=== SUMMARY ===\n');
|
||||
|
||||
// Print Successful
|
||||
workingPairs.forEach((crypto, fiats) {
|
||||
if (fiats.isNotEmpty) {
|
||||
printV('✅ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
|
||||
print('✅ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
|
||||
}
|
||||
});
|
||||
|
||||
printV('\n--------------------------------------------------\n');
|
||||
print('\n--------------------------------------------------\n');
|
||||
|
||||
// Print Failed
|
||||
failedPairs.forEach((crypto, fiats) {
|
||||
if (fiats.isNotEmpty) {
|
||||
printV('❌ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
|
||||
print('❌ ${crypto.toUpperCase()}: ${fiats.join(", ")}');
|
||||
}
|
||||
});
|
||||
|
||||
printV('\n=== DONE ===');
|
||||
print('\n=== DONE ===');
|
||||
},
|
||||
|
||||
// --- C. The Interceptor ---
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import "dart:collection";
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import './print_verbose_dummy.dart';
|
||||
|
||||
import 'localization/localization_constants.dart';
|
||||
import 'utils/utils.dart';
|
||||
@@ -37,7 +37,7 @@ Future<void> main(List<String> args) async {
|
||||
|
||||
extraInfo.forEach((key, dynamic value) async {
|
||||
if (key != srcDir) {
|
||||
printV('Wrong key: $key');
|
||||
print('Wrong key: $key');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,26 +45,29 @@ Future<void> main(List<String> args) async {
|
||||
final dir = Directory(dirPath);
|
||||
|
||||
if (!await dir.exists()) {
|
||||
printV('Wrong directory path: $dirPath');
|
||||
print('Wrong directory path: $dirPath');
|
||||
return;
|
||||
}
|
||||
|
||||
final localePath = <String, dynamic>{};
|
||||
final localePath = SplayTreeMap<String, dynamic>();
|
||||
await dir.list(recursive: false).forEach((element) {
|
||||
try {
|
||||
final shortLocale = element.path
|
||||
.split(
|
||||
'_',
|
||||
)[1]
|
||||
.split('.')[0];
|
||||
localePath[shortLocale] = element.path;
|
||||
} catch (e) {
|
||||
printV('Wrong file: ${element.path}');
|
||||
// Parse the locale from the file name (e.g. strings_pt_br.arb -> pt_BR),
|
||||
// normalizing the case so keys match LanguageService.supportedLocales.
|
||||
final fileName = element.uri.pathSegments.last;
|
||||
if (!fileName.startsWith('strings_') || !fileName.endsWith('.arb')) {
|
||||
print('Wrong file: ${element.path}');
|
||||
return;
|
||||
}
|
||||
final parts =
|
||||
fileName.substring('strings_'.length, fileName.length - '.arb'.length).split('_');
|
||||
final locale = parts.length > 1
|
||||
? '${parts.first.toLowerCase()}_${parts.sublist(1).join('_').toUpperCase()}'
|
||||
: parts.first.toLowerCase();
|
||||
localePath[locale] = element.path;
|
||||
});
|
||||
|
||||
if (!localePath.keys.contains(defaultLocale)) {
|
||||
printV("Locale list doesn't contain $defaultLocale");
|
||||
print("Locale list doesn't contain $defaultLocale");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,8 +103,14 @@ Future<void> main(List<String> args) async {
|
||||
|
||||
output += classDeclaration;
|
||||
|
||||
// Flutter matches on the language subtag, so pt_BR is emitted as Locale("pt", "BR")
|
||||
localePath.keys.forEach((key) {
|
||||
output += ' Locale("$key", ""),' + '\n';
|
||||
final parts = key.split("_");
|
||||
if (parts.length == 2) {
|
||||
output += ' Locale("${parts[0]}", "${parts[1]}"),\n';
|
||||
} else {
|
||||
output += ' Locale("$key", ""),\n';
|
||||
}
|
||||
});
|
||||
|
||||
output += part2;
|
||||
@@ -120,7 +129,7 @@ Future<void> main(List<String> args) async {
|
||||
|
||||
await File(outputPath + localeListFileName).writeAsString(locales);
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
print(e.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,6 +66,15 @@ class S implements WidgetsLocalizations {
|
||||
|
||||
@override
|
||||
String get shareButtonLabel => "shareButtonLabel";
|
||||
|
||||
@override
|
||||
String get noResultsFound => "noResultsFound";
|
||||
|
||||
@override
|
||||
String get radioButtonUnselectedLabel => "radioButtonUnselectedLabel";
|
||||
|
||||
@override
|
||||
String get searchResultsFound => "searchResultsFound";
|
||||
""";
|
||||
|
||||
const part2 = """
|
||||
|
||||
@@ -1 +1 @@
|
||||
void printV(dynamic content) => print(content);
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ void main() async {
|
||||
});
|
||||
|
||||
currentItem++;
|
||||
printV(
|
||||
print(
|
||||
"\r$currentLang/${langs.length} langs done, $currentItem/${defaultLangChangelog.length} items done");
|
||||
}
|
||||
currentLang++;
|
||||
|
||||
@@ -25,18 +25,18 @@ class TranslationChecker {
|
||||
final sourceMap = json.decode(sourceContent) as Map<String, dynamic>;
|
||||
final destinationMap = json.decode(destinationContent) as Map<String, dynamic>;
|
||||
|
||||
printV('Found ${sourceMap.length} keys in source, ${destinationMap.length} in destination');
|
||||
print('Found ${sourceMap.length} keys in source, ${destinationMap.length} in destination');
|
||||
|
||||
final keysToProcess = specificKey != null
|
||||
? (sourceMap.containsKey(specificKey) ? [specificKey] : <String>[])
|
||||
: sourceMap.keys.toList();
|
||||
|
||||
if (specificKey != null && keysToProcess.isEmpty) {
|
||||
printV('Error: Key "$specificKey" not found in source file');
|
||||
print('Error: Key "$specificKey" not found in source file');
|
||||
return;
|
||||
}
|
||||
|
||||
printV('Processing ${keysToProcess.length} translations...');
|
||||
print('Processing ${keysToProcess.length} translations...');
|
||||
|
||||
int processed = 0;
|
||||
int corrected = 0;
|
||||
@@ -57,16 +57,16 @@ class TranslationChecker {
|
||||
if (correctedTranslation != destinationValue) {
|
||||
destinationMap[key] = correctedTranslation;
|
||||
corrected++;
|
||||
printV('Processed: "$key" -> CORRECTED');
|
||||
printV(' - eng : "$sourceValue"');
|
||||
printV(' - dst orig: "$destinationValue"');
|
||||
printV(' - dst new : "$correctedTranslation"');
|
||||
print('Processed: "$key" -> CORRECTED');
|
||||
print(' - eng : "$sourceValue"');
|
||||
print(' - dst orig: "$destinationValue"');
|
||||
print(' - dst new : "$correctedTranslation"');
|
||||
await _writeArbFile(destinationArbPath, destinationMap);
|
||||
} else {
|
||||
printV('Processed: "$key" -> VERIFIED');
|
||||
print('Processed: "$key" -> VERIFIED');
|
||||
}
|
||||
} else {
|
||||
printV('Processed: "$key" -> SKIPPED (non-string)');
|
||||
print('Processed: "$key" -> SKIPPED (non-string)');
|
||||
}
|
||||
|
||||
processed++;
|
||||
@@ -74,11 +74,11 @@ class TranslationChecker {
|
||||
|
||||
await _writeArbFile(destinationArbPath, destinationMap);
|
||||
|
||||
printV('');
|
||||
printV('Summary:');
|
||||
printV('Processed: $processed keys');
|
||||
printV('Corrected: $corrected keys');
|
||||
printV('Updated: $destinationArbPath');
|
||||
print('');
|
||||
print('Summary:');
|
||||
print('Processed: $processed keys');
|
||||
print('Corrected: $corrected keys');
|
||||
print('Updated: $destinationArbPath');
|
||||
}
|
||||
|
||||
Future<String> _checkSingleTranslation({
|
||||
@@ -108,7 +108,7 @@ class TranslationChecker {
|
||||
|
||||
return currentTranslation;
|
||||
} catch (e) {
|
||||
printV('Error checking translation for key "$key": $e');
|
||||
print('Error checking translation for key "$key": $e');
|
||||
return currentTranslation;
|
||||
}
|
||||
}
|
||||
@@ -186,7 +186,7 @@ IMPORTANT: Respond with a JSON object in the exact format, you must think about
|
||||
final jsonEnd = response.lastIndexOf('}');
|
||||
|
||||
if (jsonStart == -1 || jsonEnd == -1 || jsonStart >= jsonEnd) {
|
||||
printV('No valid JSON found in LLM response');
|
||||
print('No valid JSON found in LLM response');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ IMPORTANT: Respond with a JSON object in the exact format, you must think about
|
||||
|
||||
return parsed['corrected_translation'] as String?;
|
||||
} catch (e) {
|
||||
printV('Error extracting JSON from LLM response: $e');
|
||||
print('Error extracting JSON from LLM response: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -237,7 +237,7 @@ void main(List<String> args) async {
|
||||
final results = parser.parse(args);
|
||||
|
||||
if (results['help'] as bool) {
|
||||
printV(parser.usage);
|
||||
print(parser.usage);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,24 +248,24 @@ void main(List<String> args) async {
|
||||
final model = results['model'] as String;
|
||||
|
||||
if (!File(sourcePath).existsSync()) {
|
||||
printV('Error: Source ARB file not found: $sourcePath');
|
||||
print('Error: Source ARB file not found: $sourcePath');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!File(destinationPath).existsSync()) {
|
||||
printV('Error: Destination ARB file not found: $destinationPath');
|
||||
print('Error: Destination ARB file not found: $destinationPath');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
printV('Translation Checker');
|
||||
printV('Source: $sourcePath');
|
||||
printV('Destination: $destinationPath');
|
||||
printV('Ollama URL: $ollamaUrl');
|
||||
printV('Model: $model');
|
||||
print('Translation Checker');
|
||||
print('Source: $sourcePath');
|
||||
print('Destination: $destinationPath');
|
||||
print('Ollama URL: $ollamaUrl');
|
||||
print('Model: $model');
|
||||
if (specificKey != null) {
|
||||
printV('Key: $specificKey');
|
||||
print('Key: $specificKey');
|
||||
}
|
||||
printV('');
|
||||
print('');
|
||||
|
||||
final checker = TranslationChecker(
|
||||
ollamaBaseUrl: ollamaUrl,
|
||||
@@ -278,8 +278,8 @@ void main(List<String> args) async {
|
||||
specificKey: specificKey,
|
||||
);
|
||||
} catch (e) {
|
||||
printV('Error: $e');
|
||||
printV('Run with --help for more information.');
|
||||
print('Error: $e');
|
||||
print('Run with --help for more information.');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,5 +33,5 @@ void main(List<String> args) async {
|
||||
}
|
||||
|
||||
appendStringsToArbFile(targetFileName, translations);
|
||||
printV("Success! Please add your Language Code to lib/entities/language_service.dart");
|
||||
print("Success! Please add your Language Code to lib/entities/language_service.dart");
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ import 'utils/translation/translation_constants.dart';
|
||||
import 'utils/translation/translation_utils.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
printV('Checking Consistency of all arb-files. Default: $defaultLang');
|
||||
print('Checking Consistency of all arb-files. Default: $defaultLang');
|
||||
|
||||
final doFix = args.contains("--fix");
|
||||
|
||||
if (doFix)
|
||||
printV('Auto fixing enabled!\n');
|
||||
print('Auto fixing enabled!\n');
|
||||
else
|
||||
printV('Auto fixing disabled!\nRun with arg "--fix" to enable autofix\n');
|
||||
print('Auto fixing disabled!\nRun with arg "--fix" to enable autofix\n');
|
||||
|
||||
final fileName = getArbFileName(defaultLang);
|
||||
final file = File(fileName);
|
||||
@@ -27,7 +27,7 @@ void main(List<String> args) async {
|
||||
final missingDefaults = <String, String>{};
|
||||
|
||||
missingKeys.forEach((key) {
|
||||
printV('Missing in "$lang": "$key"');
|
||||
print('Missing in "$lang": "$key"');
|
||||
if (doFix) missingDefaults[key] = arbObj[key] as String;
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ class SecretKey {
|
||||
SecretKey('changeNowCakeWalletApiKey', () => ''),
|
||||
SecretKey('changeNowMoneroApiKey', () => ''),
|
||||
SecretKey('moonPayApiKey', () => ''),
|
||||
SecretKey('moonPaySandboxApiKey', () => ''),
|
||||
SecretKey('moonPaySecretKey', () => ''),
|
||||
SecretKey('sideShiftAffiliateId', () => ''),
|
||||
SecretKey('simpleSwapApiKey', () => ''),
|
||||
@@ -46,7 +47,7 @@ class SecretKey {
|
||||
SecretKey('meldTestApiKey', () => ''),
|
||||
SecretKey('meldTestPublicKey', () => ''),
|
||||
SecretKey('moneroTestWalletSeeds', () => ''),
|
||||
SecretKey('moneroLegacyTestWalletSeeds ', () => ''),
|
||||
SecretKey('moneroLegacyTestWalletSeeds', () => ''),
|
||||
SecretKey('bitcoinTestWalletSeeds', () => ''),
|
||||
SecretKey('ethereumTestWalletSeeds', () => ''),
|
||||
SecretKey('litecoinTestWalletSeeds', () => ''),
|
||||
@@ -55,10 +56,16 @@ class SecretKey {
|
||||
SecretKey('baseTestWalletSeeds', () => ''),
|
||||
SecretKey('arbitrumTestWalletSeeds', () => ''),
|
||||
SecretKey('bscTestWalletSeeds', () => ''),
|
||||
SecretKey("robinhoodTestWalletSeeds", () => ""),
|
||||
SecretKey('solanaTestWalletSeeds', () => ''),
|
||||
SecretKey('solanaTestWalletSeeds2', () => ''),
|
||||
SecretKey('tronTestWalletSeeds', () => ''),
|
||||
SecretKey('nanoTestWalletSeeds', () => ''),
|
||||
SecretKey('wowneroTestWalletSeeds', () => ''),
|
||||
SecretKey('zanoTestWalletSeeds', () => ''),
|
||||
SecretKey('decredTestWalletSeeds', () => ''),
|
||||
SecretKey('dogeTestWalletSeeds', () => ''),
|
||||
SecretKey('zcashTestWalletSeeds', () => ''),
|
||||
SecretKey('moneroTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('bitcoinTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('ethereumTestWalletReceiveAddress', () => ''),
|
||||
@@ -68,10 +75,15 @@ class SecretKey {
|
||||
SecretKey('baseTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('arbitrumTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('bscTestWalletReceiveAddress', () => ''),
|
||||
SecretKey("robinhoodTestWalletReceiveAddress", () => ""),
|
||||
SecretKey('solanaTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('tronTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('nanoTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('wowneroTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('zanoTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('decredTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('dogeTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('zcashTestWalletReceiveAddress', () => ''),
|
||||
SecretKey('etherScanApiKey', () => ''),
|
||||
SecretKey('polygonScanApiKey', () => ''),
|
||||
SecretKey('letsExchangeBearerToken', () => ''),
|
||||
@@ -79,6 +91,7 @@ class SecretKey {
|
||||
SecretKey('stealthExBearerToken', () => ''),
|
||||
SecretKey('stealthExAdditionalFeePercent', () => ''),
|
||||
SecretKey('moneroTestWalletBlockHeight', () => ''),
|
||||
SecretKey('zcashTestWalletRestoreHeight', () => ''),
|
||||
SecretKey('chainflipApiKey', () => ''),
|
||||
SecretKey('chainflipAffiliateFee', () => ''),
|
||||
SecretKey('kryptonimApiKey', () => ''),
|
||||
@@ -96,8 +109,9 @@ class SecretKey {
|
||||
SecretKey('etherScanApiKey', () => ''),
|
||||
SecretKey('polygonScanApiKey', () => ''),
|
||||
SecretKey('moralisApiKey', () => ''),
|
||||
SecretKey('nowNodesApiKey ', () => ''),
|
||||
SecretKey('nowNodesApiKey', () => ''),
|
||||
SecretKey('blinkApiKey', () => ''),
|
||||
SecretKey('alchemyApiKey', () => ''),
|
||||
];
|
||||
|
||||
static final solanaSecrets = [
|
||||
|
||||
@@ -8,7 +8,7 @@ void appendStringToArbFile(String fileName, String name, String text, {bool forc
|
||||
final arbObj = readArbFile(file);
|
||||
|
||||
if (arbObj.containsKey(name) && !force) {
|
||||
printV("String $name already exists in $fileName!");
|
||||
print("String $name already exists in $fileName!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@ const defaultLang = "en";
|
||||
const langs = [
|
||||
"ar", "bg", "cs", "de", "en", "es", "fa", "fr", "gn", "ha", "hi", "hr", "hy", "id", "it",
|
||||
"ja", "ko", "my", "nl", "pl", "pt", "ru", "th", "tl", "tr", "uk", "ur", "yo",
|
||||
"vi", "zh-cn", "zh-tw" // zh-cn for Chinese (Simplified), zh-tw for Chinese (Traditional)
|
||||
"vi", "zh-cn" // zh-cn for Chinese (Simplified)
|
||||
];
|
||||
|
||||
@@ -38,7 +38,7 @@ Future<String> getTranslation(String text, String lang) async {
|
||||
try {
|
||||
translation = (await translator.translate(text, from: defaultLang, to: lang)).text;
|
||||
} catch (e) {
|
||||
printV("$lang, $text, $e");
|
||||
print("$lang, $text, $e");
|
||||
}
|
||||
placeholder.forEach((index, value) {
|
||||
final translatedPlaceholder = regExp.allMatches(translation).toList()[index];
|
||||
|
||||
Reference in New Issue
Block a user