diff --git a/app/build.gradle b/app/build.gradle index 438f67a22ed..3bcac06aefa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,8 +19,8 @@ android { compileSdk compile_sdk_version minSdkVersion min_sdk_version targetSdkVersion compile_sdk_version - versionCode 115 - versionName "0.39.5" + versionCode 116 + versionName "0.40.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" ksp { @@ -75,6 +75,7 @@ android { resValue "string", "arbiscanApiKey", "Z43JN5434XVNA5D73UGPWKF26G5D9MGDPZ" resValue "string", "gnosisscanApiKey", "V2J8YU15ZX9S1W3GTUV2HXM11TP2TUBRW4" resValue "string", "ftmscanApiKey", "57YQ2GIRAZNV6M5HIJYYG3XQGGNIPVV8MF" + resValue "string", "basescanApiKey", "AKEWS351FN9P9E2CFPWRWQVGHYUP7W8SUF" resValue "string", "is_release", "false" resValue "string", "guidesUrl", "https://raw.githubusercontent.com/horizontalsystems/blockchain-crypto-guides/develop/index.json" resValue "string", "faqUrl", "https://raw.githubusercontent.com/horizontalsystems/Unstoppable-Wallet-Website/master/src/faq.json" @@ -113,6 +114,7 @@ android { resValue "string", "arbiscanApiKey", "4QWW522BV13BJCZMXH1JIB2ESJ7MZTSJYI" resValue "string", "gnosisscanApiKey", "KEXFAQKDUENZ5U9CW3ZKYJEJ84ZIHH9QTY" resValue "string", "ftmscanApiKey", "JAWRPW27KEMVXMJJ9UKY63CVPH3X5V9SMP" + resValue "string", "basescanApiKey", "QU4RJVJXQCW812J3234EW9EV815TA6XC55" resValue "string", "is_release", "true" resValue "string", "guidesUrl", "https://raw.githubusercontent.com/horizontalsystems/blockchain-crypto-guides/v1.2/index.json" resValue "string", "faqUrl", "https://raw.githubusercontent.com/horizontalsystems/Unstoppable-Wallet-Website/v1.3/src/faq.json" @@ -179,6 +181,12 @@ android { substitute module('com.tinder.scarlet:message-adapter-gson:0.1.12') using module('com.github.WalletConnect.Scarlet:message-adapter-gson:1.0.0') substitute module('com.tinder.scarlet:lifecycle-android:0.1.12') using module('com.github.WalletConnect.Scarlet:lifecycle-android:1.0.0') } + + resolutionStrategy.eachDependency { details -> + if (details.requested.group == 'com.squareup.okhttp3') { + details.useVersion "4.12.0" + } + } } } @@ -232,7 +240,7 @@ dependencies { implementation 'com.squareup.retrofit2:adapter-rxjava2:2.11.0' implementation 'com.squareup.retrofit2:converter-gson:2.11.0' implementation 'com.squareup.retrofit2:converter-scalars:2.11.0' - implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0' + implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' implementation 'com.google.code.gson:gson:2.10.1' implementation "androidx.biometric:biometric:$biometric_version" @@ -269,12 +277,12 @@ dependencies { debugImplementation leakCanary // Wallet kits - implementation 'com.github.horizontalsystems:ton-kit-kmm:f1fd301' + implementation 'com.github.horizontalsystems:ton-kit-android:e907480' implementation 'com.github.horizontalsystems:bitcoin-kit-android:47db768' - implementation 'com.github.horizontalsystems:ethereum-kit-android:3d83900' + implementation 'com.github.horizontalsystems:ethereum-kit-android:a30cc3a71' implementation 'com.github.horizontalsystems:blockchain-fee-rate-kit-android:1d3bd49' implementation 'com.github.horizontalsystems:binance-chain-kit-android:c1509a2' - implementation 'com.github.horizontalsystems:market-kit-android:636ca4d' + implementation 'com.github.horizontalsystems:market-kit-android:5fe87c2' implementation 'com.github.horizontalsystems:solana-kit-android:ce738d8' implementation 'com.github.horizontalsystems:tron-kit-android:dc3dca7' // Zcash SDK diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b671a4a0c4b..95bc213110f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -357,6 +357,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt index 1d21394b41b..ca81b8c1735 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/App.kt @@ -26,7 +26,6 @@ import io.horizontalsystems.bankwallet.core.managers.AccountCleaner import io.horizontalsystems.bankwallet.core.managers.AccountManager import io.horizontalsystems.bankwallet.core.managers.AdapterManager import io.horizontalsystems.bankwallet.core.managers.AppVersionManager -import io.horizontalsystems.bankwallet.core.managers.BackgroundStateChangeListener import io.horizontalsystems.bankwallet.core.managers.BackupManager import io.horizontalsystems.bankwallet.core.managers.BalanceHiddenManager import io.horizontalsystems.bankwallet.core.managers.BaseTokenManager @@ -61,6 +60,8 @@ import io.horizontalsystems.bankwallet.core.managers.SubscriptionManager import io.horizontalsystems.bankwallet.core.managers.SystemInfoManager import io.horizontalsystems.bankwallet.core.managers.TermsManager import io.horizontalsystems.bankwallet.core.managers.TokenAutoEnableManager +import io.horizontalsystems.bankwallet.core.managers.TonAccountManager +import io.horizontalsystems.bankwallet.core.managers.TonKitManager import io.horizontalsystems.bankwallet.core.managers.TorManager import io.horizontalsystems.bankwallet.core.managers.TransactionAdapterManager import io.horizontalsystems.bankwallet.core.managers.TronAccountManager @@ -141,7 +142,6 @@ class App : CoreApp(), WorkConfiguration.Provider, ImageLoaderFactory { lateinit var btcBlockchainManager: BtcBlockchainManager lateinit var wordsManager: WordsManager lateinit var networkManager: INetworkManager - lateinit var backgroundStateChangeListener: BackgroundStateChangeListener lateinit var appConfigProvider: AppConfigProvider lateinit var adapterManager: IAdapterManager lateinit var transactionAdapterManager: TransactionAdapterManager @@ -163,6 +163,7 @@ class App : CoreApp(), WorkConfiguration.Provider, ImageLoaderFactory { lateinit var binanceKitManager: BinanceKitManager lateinit var solanaKitManager: SolanaKitManager lateinit var tronKitManager: TronKitManager + lateinit var tonKitManager: TonKitManager lateinit var numberFormatter: IAppNumberFormatter lateinit var feeCoinProvider: FeeTokenProvider lateinit var accountCleaner: IAccountCleaner @@ -275,6 +276,7 @@ class App : CoreApp(), WorkConfiguration.Provider, ImageLoaderFactory { solanaKitManager = SolanaKitManager(appConfigProvider, solanaRpcSourceManager, solanaWalletManager, backgroundManager) tronKitManager = TronKitManager(appConfigProvider, backgroundManager) + tonKitManager = TonKitManager(backgroundManager) wordsManager = WordsManager(Mnemonic()) networkManager = NetworkManager() @@ -318,6 +320,9 @@ class App : CoreApp(), WorkConfiguration.Provider, ImageLoaderFactory { ) tronAccountManager.start() + val tonAccountManager = TonAccountManager(accountManager, walletManager, tonKitManager, tokenAutoEnableManager) + tonAccountManager.start() + systemInfoManager = SystemInfoManager(appConfigProvider) languageManager = LanguageManager() @@ -344,13 +349,23 @@ class App : CoreApp(), WorkConfiguration.Provider, ImageLoaderFactory { binanceKitManager = binanceKitManager, solanaKitManager = solanaKitManager, tronKitManager = tronKitManager, + tonKitManager = tonKitManager, backgroundManager = backgroundManager, restoreSettingsManager = restoreSettingsManager, coinManager = coinManager, evmLabelManager = evmLabelManager, localStorage = localStorage ) - adapterManager = AdapterManager(walletManager, adapterFactory, btcBlockchainManager, evmBlockchainManager, binanceKitManager, solanaKitManager, tronKitManager) + adapterManager = AdapterManager( + walletManager, + adapterFactory, + btcBlockchainManager, + evmBlockchainManager, + binanceKitManager, + solanaKitManager, + tronKitManager, + tonKitManager, + ) transactionAdapterManager = TransactionAdapterManager(adapterManager, adapterFactory) feeCoinProvider = FeeTokenProvider(marketKit) @@ -358,13 +373,11 @@ class App : CoreApp(), WorkConfiguration.Provider, ImageLoaderFactory { pinComponent = PinComponent( pinSettingsStorage = pinSettingsStorage, userManager = userManager, - pinDbStorage = PinDbStorage(appDatabase.pinDao()) + pinDbStorage = PinDbStorage(appDatabase.pinDao()), + backgroundManager = backgroundManager ) - statsManager = StatsManager(appDatabase.statsDao(), localStorage, marketKit, appConfigProvider) - backgroundStateChangeListener = BackgroundStateChangeListener(pinComponent, statsManager).apply { - backgroundManager.registerListener(this) - } + statsManager = StatsManager(appDatabase.statsDao(), localStorage, marketKit, appConfigProvider, backgroundManager) rateAppManager = RateAppManager(walletManager, adapterManager, localStorage) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt index 66b25457695..9e3ed99e954 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/Interfaces.kt @@ -47,6 +47,7 @@ import io.horizontalsystems.marketkit.models.BlockchainType import io.horizontalsystems.marketkit.models.Token import io.horizontalsystems.marketkit.models.TokenQuery import io.horizontalsystems.solanakit.models.FullTransaction +import io.horizontalsystems.tonkit.FriendlyAddress import io.horizontalsystems.tronkit.transaction.Fee import io.reactivex.Flowable import io.reactivex.Observable @@ -62,7 +63,7 @@ import io.horizontalsystems.tronkit.models.Address as TronAddress interface IAdapterManager { val adaptersReadyObservable: Flowable> fun startAdapterManager() - fun refresh() + suspend fun refresh() fun getAdapterForWallet(wallet: Wallet): IAdapter? fun getAdapterForToken(token: Token): IAdapter? fun getBalanceAdapterForWallet(wallet: Wallet): IBalanceAdapter? @@ -299,9 +300,10 @@ interface IBalanceAdapter { data class BalanceData( val available: BigDecimal, val timeLocked: BigDecimal = BigDecimal.ZERO, - val notRelayed: BigDecimal = BigDecimal.ZERO + val notRelayed: BigDecimal = BigDecimal.ZERO, + val pending: BigDecimal = BigDecimal.ZERO, ) { - val total get() = available + timeLocked + notRelayed + val total get() = available + timeLocked + notRelayed + pending } interface IReceiveAdapter { @@ -399,8 +401,8 @@ interface ISendSolanaAdapter { interface ISendTonAdapter { val availableBalance: BigDecimal - suspend fun send(amount: BigDecimal, address: String, memo: String?) - suspend fun estimateFee() : BigDecimal + suspend fun send(amount: BigDecimal, address: FriendlyAddress, memo: String?) + suspend fun estimateFee(amount: BigDecimal, address: FriendlyAddress, memo: String?) : BigDecimal } interface ISendTronAdapter { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/MarketKitExtensions.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/MarketKitExtensions.kt index b5301d2f993..bc0d00c6788 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/MarketKitExtensions.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/MarketKitExtensions.kt @@ -50,6 +50,7 @@ val Token.swappable: Boolean BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne -> true @@ -70,7 +71,8 @@ val Token.protocolInfo: String } is TokenType.Eip20, is TokenType.Bep2, - is TokenType.Spl -> protocolType ?: "" + is TokenType.Spl, + is TokenType.Jetton -> protocolType ?: "" else -> "" } @@ -82,6 +84,7 @@ val Token.typeInfo: String is TokenType.Eip20 -> type.address.shorten() is TokenType.Bep2 -> type.symbol is TokenType.Spl -> type.address.shorten() + is TokenType.Jetton -> type.address.shorten() is TokenType.Unsupported -> "" } @@ -90,6 +93,7 @@ val Token.copyableTypeInfo: String? is TokenType.Eip20 -> type.address is TokenType.Bep2 -> type.symbol is TokenType.Spl -> type.address + is TokenType.Jetton -> type.address else -> null } @@ -118,6 +122,7 @@ val TokenQuery.protocolType: String? } is TokenType.Bep2 -> "BEP2" + is TokenType.Jetton -> "JETTON" else -> blockchainType.title } @@ -145,6 +150,7 @@ val TokenQuery.isSupported: Boolean BlockchainType.BinanceSmartChain, BlockchainType.Polygon, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne, BlockchainType.Gnosis, BlockchainType.Fantom, @@ -161,7 +167,7 @@ val TokenQuery.isSupported: Boolean tokenType is TokenType.Native || tokenType is TokenType.Eip20 } BlockchainType.Ton -> { - tokenType is TokenType.Native + tokenType is TokenType.Native || tokenType is TokenType.Jetton } is BlockchainType.Unsupported -> false } @@ -180,6 +186,7 @@ val Blockchain.description: String BlockchainType.Polygon -> "MATIC, ERC20 tokens" BlockchainType.Avalanche -> "AVAX, ERC20 tokens" BlockchainType.Optimism -> "L2 chain" + BlockchainType.Base -> "L2 chain" BlockchainType.ArbitrumOne -> "L2 chain" BlockchainType.Solana -> "SOL, SPL tokens" BlockchainType.Gnosis -> "xDAI, ERC20 tokens" @@ -191,6 +198,8 @@ val Blockchain.description: String fun Blockchain.eip20TokenUrl(address: String) = eip3091url?.replace("\$ref", address) +fun Blockchain.jettonUrl(address: String) = "https://tonviewer.com/$address" + fun Blockchain.bep2TokenUrl(symbol: String) = "https://explorer.binance.org/asset/$symbol" val BlockchainType.imageUrl: String @@ -212,6 +221,7 @@ private val blockchainOrderMap: Map by lazy { BlockchainType.Ton, BlockchainType.Solana, BlockchainType.Polygon, + BlockchainType.Base, BlockchainType.Avalanche, BlockchainType.Zcash, BlockchainType.BitcoinCash, @@ -240,10 +250,12 @@ val BlockchainType.tokenIconPlaceholder: Int BlockchainType.Avalanche -> R.drawable.avalanche_erc20 BlockchainType.Polygon -> R.drawable.polygon_erc20 BlockchainType.Optimism -> R.drawable.optimism_erc20 + BlockchainType.Base -> R.drawable.base_erc20 BlockchainType.ArbitrumOne -> R.drawable.arbitrum_erc20 BlockchainType.Gnosis -> R.drawable.gnosis_erc20 BlockchainType.Fantom -> R.drawable.fantom_erc20 BlockchainType.Tron -> R.drawable.tron_trc20 + BlockchainType.Ton -> R.drawable.the_open_network_jetton else -> R.drawable.coin_placeholder } @@ -262,6 +274,7 @@ val BlockchainType.title: String BlockchainType.ArbitrumOne -> "ArbitrumOne" BlockchainType.BinanceChain -> "BNB Beacon Coin" BlockchainType.Optimism -> "Optimism" + BlockchainType.Base -> "Base" BlockchainType.Solana -> "Solana" BlockchainType.Gnosis -> "Gnosis" BlockchainType.Fantom -> "Fantom" @@ -287,6 +300,7 @@ val BlockchainType.brandColor: Color? BlockchainType.Polygon -> Color(0xFF8247E5) BlockchainType.Avalanche -> Color(0xFFD74F49) BlockchainType.Optimism -> Color(0xFFEB3431) + BlockchainType.Base -> Color(0xFF2759F6) BlockchainType.ArbitrumOne -> Color(0xFF96BEDC) else -> null } @@ -322,6 +336,7 @@ fun BlockchainType.supports(accountType: AccountType): Boolean { || this == BlockchainType.Polygon || this == BlockchainType.Avalanche || this == BlockchainType.Optimism + || this == BlockchainType.Base || this == BlockchainType.ArbitrumOne || this == BlockchainType.Gnosis || this == BlockchainType.Fantom @@ -331,6 +346,7 @@ fun BlockchainType.supports(accountType: AccountType): Boolean { || this == BlockchainType.Polygon || this == BlockchainType.Avalanche || this == BlockchainType.Optimism + || this == BlockchainType.Base || this == BlockchainType.ArbitrumOne || this == BlockchainType.Gnosis || this == BlockchainType.Fantom @@ -565,6 +581,7 @@ val BlockchainType.Companion.supported: List BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne, BlockchainType.Gnosis, BlockchainType.Fantom, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/BaseTonAdapter.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/BaseTonAdapter.kt new file mode 100644 index 00000000000..0df243f25d7 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/BaseTonAdapter.kt @@ -0,0 +1,32 @@ +package io.horizontalsystems.bankwallet.core.adapters + +import io.horizontalsystems.bankwallet.core.IAdapter +import io.horizontalsystems.bankwallet.core.IBalanceAdapter +import io.horizontalsystems.bankwallet.core.IReceiveAdapter +import io.horizontalsystems.bankwallet.core.managers.TonKitWrapper +import io.horizontalsystems.bankwallet.core.managers.statusInfo +import io.horizontalsystems.tonkit.models.Network + +abstract class BaseTonAdapter( + tonKitWrapper: TonKitWrapper, + val decimals: Int +) : IAdapter, IBalanceAdapter, IReceiveAdapter { + + val tonKit = tonKitWrapper.tonKit + + override val debugInfo: String + get() = "" + + val statusInfo: Map + get() = tonKit.statusInfo() + + // IReceiveAdapter + + override val receiveAddress: String + get() = tonKit.receiveAddress.toUserFriendly(false) + + override val isMainNet: Boolean + get() = tonKit.network == Network.MainNet + + // ISendTronAdapter +} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/BitcoinBaseAdapter.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/BitcoinBaseAdapter.kt index 5c138a9add5..57beb313562 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/BitcoinBaseAdapter.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/BitcoinBaseAdapter.kt @@ -32,6 +32,7 @@ import io.horizontalsystems.bitcoincore.storage.FullTransaction import io.horizontalsystems.bitcoincore.storage.UnspentOutput import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.hodler.HodlerOutputData import io.horizontalsystems.hodler.HodlerPlugin import io.horizontalsystems.marketkit.models.Token @@ -40,6 +41,10 @@ import io.reactivex.Flowable import io.reactivex.Observable import io.reactivex.Single import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import java.math.BigDecimal import java.math.RoundingMode import java.util.Date @@ -47,28 +52,16 @@ import java.util.Date abstract class BitcoinBaseAdapter( open val kit: AbstractKit, open val syncMode: BitcoinCore.SyncMode, - backgroundManager: BackgroundManager, + private val backgroundManager: BackgroundManager, val wallet: Wallet, private val confirmationsThreshold: Int, protected val decimal: Int = 8 -) : IAdapter, ITransactionsAdapter, IBalanceAdapter, IReceiveAdapter, BackgroundManager.Listener { +) : IAdapter, ITransactionsAdapter, IBalanceAdapter, IReceiveAdapter { - init { - backgroundManager.registerListener(this) - } + private val scope = CoroutineScope(Dispatchers.Default) abstract val satoshisInBitcoin: BigDecimal - override fun willEnterForeground() { - super.willEnterForeground() - kit.onEnterForeground() - } - - override fun didEnterBackground() { - super.didEnterBackground() - kit.onEnterBackground() - } - // // Adapter implementation // @@ -173,10 +166,12 @@ abstract class BitcoinBaseAdapter( override fun start() { kit.start() + subscribeToEvents() } override fun stop() { kit.stop() + scope.cancel() } override fun refresh() { @@ -215,6 +210,24 @@ abstract class BitcoinBaseAdapter( } } + private fun subscribeToEvents() { + scope.launch { + backgroundManager.stateFlow.collect { state -> + when (state) { + BackgroundManagerState.EnterForeground -> { + kit.onEnterForeground() + } + BackgroundManagerState.EnterBackground -> { + kit.onEnterBackground() + } + BackgroundManagerState.AllActivitiesDestroyed -> { + + } + } + } + } + } + override fun getRawTransaction(transactionHash: String): String? { return kit.getRawTransaction(transactionHash) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/JettonAdapter.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/JettonAdapter.kt new file mode 100644 index 00000000000..91bab656f70 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/JettonAdapter.kt @@ -0,0 +1,94 @@ +package io.horizontalsystems.bankwallet.core.adapters + +import io.horizontalsystems.bankwallet.core.AdapterState +import io.horizontalsystems.bankwallet.core.BalanceData +import io.horizontalsystems.bankwallet.core.ISendTonAdapter +import io.horizontalsystems.bankwallet.core.managers.TonKitWrapper +import io.horizontalsystems.bankwallet.core.managers.toAdapterState +import io.horizontalsystems.bankwallet.entities.Wallet +import io.horizontalsystems.tonkit.Address +import io.horizontalsystems.tonkit.FriendlyAddress +import io.reactivex.BackpressureStrategy +import io.reactivex.Flowable +import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import java.math.BigDecimal + +class JettonAdapter( + tonKitWrapper: TonKitWrapper, + addressStr: String, + wallet: Wallet, +) : BaseTonAdapter(tonKitWrapper, wallet.decimal), ISendTonAdapter { + + private val address = Address.parse(addressStr) + private var jettonBalance = tonKit.jettonBalanceMap[address] + + private val balanceUpdatedSubject: PublishSubject = PublishSubject.create() + private val balanceStateUpdatedSubject: PublishSubject = PublishSubject.create() + + private val balance: BigDecimal + get() = jettonBalance?.balance?.toBigDecimal()?.movePointLeft(decimals) + ?: BigDecimal.ZERO + + override var balanceState: AdapterState = AdapterState.Syncing() + override val balanceStateUpdatedFlowable: Flowable + get() = balanceStateUpdatedSubject.toFlowable(BackpressureStrategy.BUFFER) + override val balanceData: BalanceData + get() = BalanceData(balance) + override val balanceUpdatedFlowable: Flowable + get() = balanceUpdatedSubject.toFlowable(BackpressureStrategy.BUFFER) + + private val coroutineScope = CoroutineScope(Dispatchers.Default) + + override fun start() { + coroutineScope.launch { + tonKit.jettonBalanceMapFlow.collect { jettonBalanceMap -> + jettonBalance = jettonBalanceMap[address] + balanceUpdatedSubject.onNext(Unit) + } + } + coroutineScope.launch { + tonKit.jettonSyncStateFlow.collect { + balanceState = it.toAdapterState() + balanceStateUpdatedSubject.onNext(Unit) + } + } + } + + override fun stop() { + coroutineScope.cancel() + } + + override fun refresh() { + } + + override val availableBalance: BigDecimal + get() = balance + + override suspend fun send(amount: BigDecimal, address: FriendlyAddress, memo: String?) { + tonKit.send( + jettonBalance?.walletAddress!!, + address, + amount.movePointRight(decimals).toBigInteger(), + memo + ) + } + + override suspend fun estimateFee( + amount: BigDecimal, + address: FriendlyAddress, + memo: String?, + ): BigDecimal { + val estimateFee = tonKit.estimateFee( + jettonBalance?.walletAddress!!, + address, + amount.movePointRight(decimals).toBigInteger(), + memo + ) + + return estimateFee.toBigDecimal(decimals).stripTrailingZeros() + } +} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonAdapter.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonAdapter.kt index c55f2bc1489..cb2eab77763 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonAdapter.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonAdapter.kt @@ -1,128 +1,67 @@ package io.horizontalsystems.bankwallet.core.adapters import io.horizontalsystems.bankwallet.core.AdapterState -import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.bankwallet.core.BalanceData -import io.horizontalsystems.bankwallet.core.IAdapter -import io.horizontalsystems.bankwallet.core.IBalanceAdapter -import io.horizontalsystems.bankwallet.core.IReceiveAdapter import io.horizontalsystems.bankwallet.core.ISendTonAdapter -import io.horizontalsystems.bankwallet.core.ITransactionsAdapter -import io.horizontalsystems.bankwallet.core.UnsupportedAccountException -import io.horizontalsystems.bankwallet.entities.AccountType -import io.horizontalsystems.bankwallet.entities.LastBlockInfo +import io.horizontalsystems.bankwallet.core.managers.TonKitWrapper +import io.horizontalsystems.bankwallet.core.managers.toAdapterState import io.horizontalsystems.bankwallet.entities.TransactionValue -import io.horizontalsystems.bankwallet.entities.Wallet import io.horizontalsystems.bankwallet.entities.transactionrecords.TransactionRecord -import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType import io.horizontalsystems.bankwallet.modules.transactions.TransactionSource import io.horizontalsystems.bankwallet.modules.transactions.TransactionStatus -import io.horizontalsystems.hdwalletkit.Curve -import io.horizontalsystems.hdwalletkit.HDWallet import io.horizontalsystems.marketkit.models.Token -import io.horizontalsystems.tonkit.ConnectionManager -import io.horizontalsystems.tonkit.DriverFactory -import io.horizontalsystems.tonkit.SyncState -import io.horizontalsystems.tonkit.TonAddress -import io.horizontalsystems.tonkit.TonKit -import io.horizontalsystems.tonkit.TonKitFactory -import io.horizontalsystems.tonkit.TonTransactionWithTransfers -import io.horizontalsystems.tonkit.TonTransfer -import io.horizontalsystems.tonkit.TransactionType +import io.horizontalsystems.tonkit.FriendlyAddress +import io.horizontalsystems.tonkit.core.TonKit.SendAmount +import io.horizontalsystems.tonkit.models.Account +import io.horizontalsystems.tonkit.models.Event import io.reactivex.BackpressureStrategy import io.reactivex.Flowable -import io.reactivex.Single import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import kotlinx.coroutines.rx2.asFlowable -import kotlinx.coroutines.rx2.rxSingle import java.math.BigDecimal +import kotlin.math.absoluteValue -class TonAdapter( - private val wallet: Wallet, -) : IAdapter, IBalanceAdapter, IReceiveAdapter, ITransactionsAdapter, ISendTonAdapter { - - private val decimals = 9 +class TonAdapter(tonKitWrapper: TonKitWrapper) : BaseTonAdapter(tonKitWrapper, 9), ISendTonAdapter { private val balanceUpdatedSubject: PublishSubject = PublishSubject.create() private val balanceStateUpdatedSubject: PublishSubject = PublishSubject.create() - private val transactionsStateUpdatedSubject: PublishSubject = PublishSubject.create() - private val tonKit: TonKit private val coroutineScope = CoroutineScope(Dispatchers.Default) - private var balance = BigDecimal.ZERO - - init { - val accountType = wallet.account.type - val tonKitFactory = TonKitFactory(DriverFactory(App.instance), ConnectionManager(App.instance)) - tonKit = when (accountType) { - is AccountType.Mnemonic -> { - val hdWallet = HDWallet(accountType.seed, 607, HDWallet.Purpose.BIP44, Curve.Ed25519) - val privateKey = hdWallet.privateKey(0) - var privateKeyBytes = privateKey.privKeyBytes - if (privateKeyBytes.size > 32) { - privateKeyBytes = privateKeyBytes.copyOfRange(1, privateKeyBytes.size) - } - tonKitFactory.create(privateKeyBytes, wallet.account.id) - } - - is AccountType.TonAddress -> { - tonKitFactory.createWatch(accountType.address, wallet.account.id) - } - - else -> { - throw UnsupportedAccountException() - } - } - } + private var balance = getBalanceFromAccount(tonKit.account) override fun start() { coroutineScope.launch { - tonKit.balanceFlow.collect { - balance = it.toBigDecimal().movePointLeft(decimals) + tonKit.accountFlow.collect { account -> + balance = getBalanceFromAccount(account) balanceUpdatedSubject.onNext(Unit) } } coroutineScope.launch { - tonKit.balanceSyncStateFlow.collect { - balanceState = convertToAdapterState(it) + tonKit.syncStateFlow.collect { + balanceState = it.toAdapterState() balanceStateUpdatedSubject.onNext(Unit) } } - coroutineScope.launch { - tonKit.transactionsSyncStateFlow.collect { - transactionsState = convertToAdapterState(it) - transactionsStateUpdatedSubject.onNext(Unit) - } - } - tonKit.start() + } + + private fun getBalanceFromAccount(account: Account?): BigDecimal { + return account?.balance?.toBigDecimal()?.movePointLeft(decimals) ?: BigDecimal.ZERO } override fun stop() { - tonKit.stop() coroutineScope.cancel() } override fun refresh() { -// tonKit.refresh() } override val debugInfo: String get() = "" - val statusInfo: Map - get() = buildMap { - put("Balance Sync State", balanceState) - put("Transaction Sync State", transactionsState) - } - - override var balanceState: AdapterState = AdapterState.Syncing() override val balanceStateUpdatedFlowable: Flowable get() = balanceStateUpdatedSubject.toFlowable(BackpressureStrategy.BUFFER) @@ -131,179 +70,108 @@ class TonAdapter( override val balanceUpdatedFlowable: Flowable get() = balanceUpdatedSubject.toFlowable(BackpressureStrategy.BUFFER) - override val receiveAddress = tonKit.receiveAddress - override val isMainNet = true - - override val explorerTitle = "tonscan.org" - override var transactionsState: AdapterState = AdapterState.Syncing() - override val transactionsStateUpdatedFlowable: Flowable - get() = transactionsStateUpdatedSubject.toFlowable(BackpressureStrategy.BUFFER) - override val lastBlockInfo: LastBlockInfo? - get() = null - override val lastBlockUpdatedFlowable: Flowable - get() = Flowable.empty() - - override fun getTransactionsAsync( - from: TransactionRecord?, - token: Token?, - limit: Int, - transactionType: FilterTransactionType, - address: String?, - ): Single> { - - return rxSingle { - val tonTransactionType = when (transactionType) { - FilterTransactionType.All -> null - FilterTransactionType.Incoming -> TransactionType.Incoming - FilterTransactionType.Outgoing -> TransactionType.Outgoing - FilterTransactionType.Swap -> return@rxSingle listOf() - FilterTransactionType.Approve -> return@rxSingle listOf() - } + override val availableBalance: BigDecimal + get() = balance - tonKit - .transactions(from?.transactionHash, tonTransactionType, address, limit.toLong()) - .map { - createTransactionRecord(it) - } - } + private fun getSendAmount(amount: BigDecimal) = when { + amount.compareTo(availableBalance) == 0 -> SendAmount.Max + else -> SendAmount.Amount(amount.movePointRight(decimals).toBigInteger()) } - private fun createTransactionRecord(transaction: TonTransactionWithTransfers): TransactionRecord { - val amount = transaction.amount?.toBigDecimal()?.movePointLeft(decimals) - - val value = if (transaction.type == TransactionType.Outgoing) { - amount?.negate() - } else { - amount - } ?: BigDecimal.ZERO - - val fee = transaction.fee?.toBigDecimal()?.movePointLeft(decimals) - - val type = when (transaction.type) { - TransactionType.Incoming -> TonTransactionRecord.Type.Incoming - TransactionType.Outgoing -> TonTransactionRecord.Type.Outgoing - TransactionType.Unknown -> TonTransactionRecord.Type.Unknown - } - - return TonTransactionRecord( - uid = transaction.hash, - transactionHash = transaction.hash, - logicalTime = transaction.lt, - blockHeight = null, - confirmationsThreshold = null, - timestamp = transaction.timestamp, - source = wallet.transactionSource, - mainValue = TransactionValue.CoinValue(wallet.token, value), - fee = fee?.let { TransactionValue.CoinValue(wallet.token, it) }, - memo = transaction.memo, - type = type, - transfers = transaction.transfers.map { createTransferRecord(it) } - ) + override suspend fun send(amount: BigDecimal, address: FriendlyAddress, memo: String?) { + tonKit.send(address, getSendAmount(amount), memo) } - private fun createTransferRecord(transfer: TonTransfer): TonTransactionTransfer { - val amount = transfer.amount.toBigDecimal().movePointLeft(decimals) - return TonTransactionTransfer( - src = transfer.src.getNonBounceable(), - dest = transfer.dest.getNonBounceable(), - amount = TransactionValue.CoinValue(wallet.token, amount), - ) + override suspend fun estimateFee(amount: BigDecimal, address: FriendlyAddress, memo: String?): BigDecimal { + val estimateFee = tonKit.estimateFee(address, getSendAmount(amount), memo) + return estimateFee.toBigDecimal(decimals).stripTrailingZeros() } - override fun getTransactionRecordsFlowable( - token: Token?, - transactionType: FilterTransactionType, - address: String?, - ): Flowable> { - val tonTransactionType = when (transactionType) { - FilterTransactionType.All -> null - FilterTransactionType.Incoming -> TransactionType.Incoming - FilterTransactionType.Outgoing -> TransactionType.Outgoing - FilterTransactionType.Swap -> return Flowable.empty() - FilterTransactionType.Approve -> return Flowable.empty() + companion object { + fun getAmount(kitAmount: Long): BigDecimal { + return kitAmount.toBigDecimal().movePointLeft(9).stripTrailingZeros() } - - return tonKit.newTransactionsFlow - .map { - var filtered = it - - if (tonTransactionType != null) { - filtered = filtered.filter { it.type == tonTransactionType } - } - - if (address != null) { - val rawAddress = TonAddress.parse(address).toRaw() - filtered = filtered.filter { - it.transfers.any { - it.src.toRaw() == rawAddress || it.dest.toRaw() == rawAddress - } - } - } - - filtered - } - .filter { it.isNotEmpty() } - .map { - it.map { - createTransactionRecord(it) - } - } - .asFlowable() - } - - override fun getTransactionUrl(transactionHash: String): String { - return "https://tonscan.org/tx/$transactionHash" - } - - private fun convertToAdapterState(syncState: SyncState) = when (syncState) { - is SyncState.NotSynced -> AdapterState.NotSynced(syncState.error) - is SyncState.Synced -> AdapterState.Synced - is SyncState.Syncing -> AdapterState.Syncing() - } - - override val availableBalance: BigDecimal - get() = balance - - override suspend fun send(amount: BigDecimal, address: String, memo: String?) { - tonKit.send(address, amount.movePointRight(decimals).toBigInteger().toString(), memo) - } - - override suspend fun estimateFee(): BigDecimal { - return tonKit.estimateFee().toBigDecimal().movePointLeft(decimals) } } class TonTransactionRecord( - uid: String, - transactionHash: String, - val logicalTime: Long, - blockHeight: Int?, - confirmationsThreshold: Int?, - timestamp: Long, - failed: Boolean = false, - spam: Boolean = false, source: TransactionSource, - override val mainValue: TransactionValue, - val fee: TransactionValue?, - val memo: String?, - val type: Type, - val transfers: List + event: Event, + baseToken: Token, + val actions: List ) : TransactionRecord( - uid, - transactionHash, - 0, - blockHeight, - confirmationsThreshold, - timestamp, - failed, - spam, - source, + uid = event.id, + transactionHash = event.id, + transactionIndex = 0, + blockHeight = null, + confirmationsThreshold = null, + timestamp = event.timestamp, + failed = false, + spam = event.scam, + source = source, ) { - enum class Type { - Incoming, Outgoing, Unknown + val lt = event.lt + val inProgress = event.inProgress + val fee = TransactionValue.CoinValue(baseToken, TonAdapter.getAmount(event.extra.absoluteValue)) + + override fun status(lastBlockHeight: Int?) = when { + inProgress -> TransactionStatus.Pending + else -> TransactionStatus.Completed } - override fun status(lastBlockHeight: Int?) = TransactionStatus.Completed + override val mainValue: TransactionValue? + get() = actions.singleOrNull()?.let { action -> + when (val type = action.type) { + is Action.Type.Receive -> type.value + is Action.Type.Send -> type.value + is Action.Type.Burn -> type.value + is Action.Type.Mint -> type.value + is Action.Type.ContractCall -> type.value + is Action.Type.ContractDeploy, + is Action.Type.Swap, + is Action.Type.Unsupported -> null + } + } + + data class Action( + val type: Type, + val status: TransactionStatus + ) { + sealed class Type { + data class Send( + val value: TransactionValue, + val to: String, + val sentToSelf: Boolean, + val comment: String?, + ) : Type() + + data class Receive( + val value: TransactionValue, + val from: String, + val comment: String?, + ) : Type() + + data class Burn(val value: TransactionValue) : Type() + + data class Mint(val value: TransactionValue) : Type() + + data class Swap( + val routerName: String?, + val routerAddress: String, + val valueIn: TransactionValue, + val valueOut: TransactionValue + ) : Type() + + data class ContractDeploy(val interfaces: List) : Type() + + data class ContractCall( + val address: String, + val value: TransactionValue, + val operation: String + ) : Type() + + data class Unsupported(val type: String) : Type() + } + } } -data class TonTransactionTransfer(val src: String, val dest: String, val amount: TransactionValue.CoinValue) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonTransactionConverter.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonTransactionConverter.kt new file mode 100644 index 00000000000..be273d46b88 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonTransactionConverter.kt @@ -0,0 +1,174 @@ +package io.horizontalsystems.bankwallet.core.adapters + +import io.horizontalsystems.bankwallet.core.ICoinManager +import io.horizontalsystems.bankwallet.entities.TransactionValue +import io.horizontalsystems.bankwallet.modules.transactions.TransactionSource +import io.horizontalsystems.bankwallet.modules.transactions.TransactionStatus +import io.horizontalsystems.marketkit.models.BlockchainType +import io.horizontalsystems.marketkit.models.Token +import io.horizontalsystems.marketkit.models.TokenQuery +import io.horizontalsystems.marketkit.models.TokenType +import io.horizontalsystems.tonkit.Address +import io.horizontalsystems.tonkit.models.AccountAddress +import io.horizontalsystems.tonkit.models.Action +import io.horizontalsystems.tonkit.models.Event +import io.horizontalsystems.tonkit.models.Jetton +import java.math.BigDecimal +import java.math.BigInteger + +class TonTransactionConverter( + private val address: Address, + private val coinManager: ICoinManager, + private val source: TransactionSource, + private val baseToken: Token +) { + + fun createTransactionRecord(event: Event): TonTransactionRecord { + val actions = event.actions.map { action -> + val status = when (action.status) { + Action.Status.OK -> TransactionStatus.Completed + Action.Status.FAILED -> TransactionStatus.Failed + } + + TonTransactionRecord.Action( + type = getActionType(action), + status = status + ) + } + + return TonTransactionRecord(source, event, baseToken, actions) + } + + private fun convertAmount(amount: BigInteger, decimal: Int, negative: Boolean): BigDecimal { + var significandAmount = amount.toBigDecimal().movePointLeft(decimal).stripTrailingZeros() + + if (significandAmount.compareTo(BigDecimal.ZERO) == 0) { + return BigDecimal.ZERO + } + + if (negative) { + significandAmount = significandAmount.negate() + } + + return significandAmount + } + + private fun tonValue(value: BigInteger, negative: Boolean): TransactionValue { + val amount = convertAmount(value, baseToken.decimals, negative) + return TransactionValue.CoinValue(baseToken, amount) + } + + private fun jettonValue(jetton: Jetton, value: BigInteger, negative: Boolean): TransactionValue { + val query = TokenQuery(BlockchainType.Ton, TokenType.Jetton(jetton.address.toUserFriendly(bounceable = true))) + + val token = coinManager.getToken(query) + + return if (token != null) { + TransactionValue.CoinValue(token, convertAmount(value, token.decimals, negative)) + } else { + TransactionValue.JettonValue( + jetton.name, + jetton.symbol, + jetton.decimals, + convertAmount(value, jetton.decimals, negative), + jetton.image + ) + } + } + + private fun format(address: AccountAddress): String { + return address.address.toUserFriendly(bounceable = !address.isWallet) + } + + private fun getActionType(action: Action): TonTransactionRecord.Action.Type { + action.tonTransfer?.let { tonTransfer -> + return when { + tonTransfer.sender.address == address -> { + TonTransactionRecord.Action.Type.Send( + value = tonValue(tonTransfer.amount, true), + to = format(tonTransfer.recipient), + sentToSelf = tonTransfer.recipient.address == address, + comment = tonTransfer.comment + ) + } + + tonTransfer.recipient.address == address -> { + TonTransactionRecord.Action.Type.Receive( + value = tonValue(tonTransfer.amount, false), + from = format(tonTransfer.sender), + comment = tonTransfer.comment + ) + } + + else -> TonTransactionRecord.Action.Type.Unsupported("Ton Transfer") + } + } + + action.jettonTransfer?.let { jettonTransfer -> + val recipient = jettonTransfer.recipient + val sender = jettonTransfer.sender + + return when { + jettonTransfer.sender?.address == address && recipient != null -> { + TonTransactionRecord.Action.Type.Send( + value = jettonValue(jettonTransfer.jetton, jettonTransfer.amount, true), + to = format(recipient), + sentToSelf = jettonTransfer.recipient?.address == address, + comment = jettonTransfer.comment + ) + } + + jettonTransfer.recipient?.address == address && sender != null -> { + TonTransactionRecord.Action.Type.Receive( + value = jettonValue(jettonTransfer.jetton, jettonTransfer.amount, false), + from = format(sender), + comment = jettonTransfer.comment + ) + } + + else -> TonTransactionRecord.Action.Type.Unsupported("Jetton Transfer") + } + } + + action.jettonBurn?.let { jettonBurn -> + return TonTransactionRecord.Action.Type.Burn( + value = jettonValue(jettonBurn.jetton, jettonBurn.amount, true) + ) + } + + action.jettonMint?.let { jettonMint -> + return TonTransactionRecord.Action.Type.Mint( + value = jettonValue(jettonMint.jetton, jettonMint.amount, false) + ) + } + + action.contractDeploy?.let { contractDeploy -> + return TonTransactionRecord.Action.Type.ContractDeploy( + interfaces = contractDeploy.interfaces + ) + } + + action.jettonSwap?.let { jettonSwap -> + return TonTransactionRecord.Action.Type.Swap( + routerName = jettonSwap.router.name, + routerAddress = format(jettonSwap.router), + valueIn = jettonSwap.jettonMasterIn?.let { + jettonValue(it, jettonSwap.amountIn, true) + } ?: tonValue(jettonSwap.tonIn ?: BigInteger.ZERO, true), + valueOut = jettonSwap.jettonMasterOut?.let { + jettonValue(it, jettonSwap.amountOut, false) + } ?: tonValue(jettonSwap.tonOut ?: BigInteger.ZERO, false), + ) + } + + action.smartContractExec?.let { smartContractExec -> + return TonTransactionRecord.Action.Type.ContractCall( + address = format(smartContractExec.contract), + value = tonValue(smartContractExec.tonAttached, true), + operation = smartContractExec.operation + ) + } + + return TonTransactionRecord.Action.Type.Unsupported(action.type.name) + } +} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonTransactionsAdapter.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonTransactionsAdapter.kt new file mode 100644 index 00000000000..ca06b6a8f75 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/TonTransactionsAdapter.kt @@ -0,0 +1,117 @@ +package io.horizontalsystems.bankwallet.core.adapters + +import io.horizontalsystems.bankwallet.core.AdapterState +import io.horizontalsystems.bankwallet.core.ITransactionsAdapter +import io.horizontalsystems.bankwallet.core.managers.TonKitWrapper +import io.horizontalsystems.bankwallet.core.managers.toAdapterState +import io.horizontalsystems.bankwallet.entities.LastBlockInfo +import io.horizontalsystems.bankwallet.entities.transactionrecords.TransactionRecord +import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType +import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType.All +import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType.Approve +import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType.Incoming +import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType.Outgoing +import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType.Swap +import io.horizontalsystems.marketkit.models.Token +import io.horizontalsystems.marketkit.models.TokenType +import io.horizontalsystems.tonkit.Address +import io.horizontalsystems.tonkit.models.Tag +import io.horizontalsystems.tonkit.models.TagQuery +import io.reactivex.Flowable +import io.reactivex.Single +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.rx2.asFlowable +import kotlinx.coroutines.rx2.rxSingle + +class TonTransactionsAdapter( + tonKitWrapper: TonKitWrapper, + private val tonTransactionConverter: TonTransactionConverter, +) : ITransactionsAdapter { + + private val tonKit = tonKitWrapper.tonKit + + override val explorerTitle = "tonviewer.com" + override val transactionsState: AdapterState + get() = tonKit.eventSyncStateFlow.value.toAdapterState() + override val transactionsStateUpdatedFlowable: Flowable + get() = tonKit.eventSyncStateFlow.asFlowable().map {} + override val lastBlockInfo: LastBlockInfo? + get() = null + override val lastBlockUpdatedFlowable: Flowable + get() = Flowable.empty() + + override fun getTransactionsAsync( + from: TransactionRecord?, + token: Token?, + limit: Int, + transactionType: FilterTransactionType, + address: String?, + ): Single> = try { + val tagQuery = getTagQuery(token, transactionType, address) + val beforeLt = (from as TonTransactionRecord?)?.lt + + rxSingle { + tonKit.events(tagQuery, beforeLt, limit = limit) + .map { + tonTransactionConverter.createTransactionRecord(it) + } + } + } catch (e: NotSupportedException) { + Single.just(listOf()) + } + + private fun getTagQuery(token: Token?, transactionType: FilterTransactionType, address: String?): TagQuery { + var platform: Tag.Platform? = null + var jettonAddress: Address? = null + + val tokenType = token?.type + + if (tokenType == TokenType.Native) { + platform = Tag.Platform.Native + } else if (tokenType is TokenType.Jetton) { + platform = Tag.Platform.Jetton + jettonAddress = Address.parse(tokenType.address) + } + + val tagType = when (transactionType) { + All -> null + Incoming -> Tag.Type.Incoming + Outgoing -> Tag.Type.Outgoing + Swap -> Tag.Type.Swap + Approve -> throw NotSupportedException() + } + + return TagQuery( + tagType, + platform, + jettonAddress, + address?.let { Address.parse(it) } + ) + } + + override fun getTransactionRecordsFlowable( + token: Token?, + transactionType: FilterTransactionType, + address: String?, + ): Flowable> = try { + val tagQuery = getTagQuery(token, transactionType, address) + + tonKit + .eventFlow(tagQuery) + .map { eventInfo -> + eventInfo.events.map { + tonTransactionConverter.createTransactionRecord(it) + } + } + .asFlowable() + + } catch (e: NotSupportedException) { + Flowable.empty() + } + + override fun getTransactionUrl(transactionHash: String): String { + return "https://tonviewer.com/transaction/$transactionHash" + } + + class NotSupportedException : Exception() +} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/zcash/ZcashAdapter.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/zcash/ZcashAdapter.kt index 2e2aab03f62..ae7542d08ff 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/zcash/ZcashAdapter.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/adapters/zcash/ZcashAdapter.kt @@ -156,7 +156,7 @@ class ZcashAdapter( get() = adapterStateUpdatedSubject.toFlowable(BackpressureStrategy.BUFFER) override val balanceData: BalanceData - get() = BalanceData(balance, balanceLocked) + get() = BalanceData(balance, pending = balancePending) val statusInfo: Map get() { @@ -173,7 +173,7 @@ class ZcashAdapter( return walletBalance.available.convertZatoshiToZec(decimalCount) } - private val balanceLocked: BigDecimal + private val balancePending: BigDecimal get() { val walletBalance = synchronizer.saplingBalances.value ?: return BigDecimal.ZERO return walletBalance.pending.convertZatoshiToZec(decimalCount) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/factories/AdapterFactory.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/factories/AdapterFactory.kt index b75b557c36e..1fbb11b69cc 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/factories/AdapterFactory.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/factories/AdapterFactory.kt @@ -14,12 +14,15 @@ import io.horizontalsystems.bankwallet.core.adapters.ECashAdapter import io.horizontalsystems.bankwallet.core.adapters.Eip20Adapter import io.horizontalsystems.bankwallet.core.adapters.EvmAdapter import io.horizontalsystems.bankwallet.core.adapters.EvmTransactionsAdapter +import io.horizontalsystems.bankwallet.core.adapters.JettonAdapter import io.horizontalsystems.bankwallet.core.adapters.LitecoinAdapter import io.horizontalsystems.bankwallet.core.adapters.SolanaAdapter import io.horizontalsystems.bankwallet.core.adapters.SolanaTransactionConverter import io.horizontalsystems.bankwallet.core.adapters.SolanaTransactionsAdapter import io.horizontalsystems.bankwallet.core.adapters.SplAdapter import io.horizontalsystems.bankwallet.core.adapters.TonAdapter +import io.horizontalsystems.bankwallet.core.adapters.TonTransactionConverter +import io.horizontalsystems.bankwallet.core.adapters.TonTransactionsAdapter import io.horizontalsystems.bankwallet.core.adapters.Trc20Adapter import io.horizontalsystems.bankwallet.core.adapters.TronAdapter import io.horizontalsystems.bankwallet.core.adapters.TronTransactionConverter @@ -32,6 +35,7 @@ import io.horizontalsystems.bankwallet.core.managers.EvmLabelManager import io.horizontalsystems.bankwallet.core.managers.EvmSyncSourceManager import io.horizontalsystems.bankwallet.core.managers.RestoreSettingsManager import io.horizontalsystems.bankwallet.core.managers.SolanaKitManager +import io.horizontalsystems.bankwallet.core.managers.TonKitManager import io.horizontalsystems.bankwallet.core.managers.TronKitManager import io.horizontalsystems.bankwallet.entities.Wallet import io.horizontalsystems.bankwallet.modules.transactions.TransactionSource @@ -48,6 +52,7 @@ class AdapterFactory( private val binanceKitManager: BinanceKitManager, private val solanaKitManager: SolanaKitManager, private val tronKitManager: TronKitManager, + private val tonKitManager: TonKitManager, private val backgroundManager: BackgroundManager, private val restoreSettingsManager: RestoreSettingsManager, private val coinManager: ICoinManager, @@ -85,6 +90,12 @@ class AdapterFactory( return Trc20Adapter(tronKitWrapper, address, wallet) } + private fun getJettonAdapter(wallet: Wallet, address: String): IAdapter { + val tonKitWrapper = tonKitManager.getTonKitWrapper(wallet.account) + + return JettonAdapter(tonKitWrapper, address, wallet) + } + fun getAdapterOrNull(wallet: Wallet) = try { getAdapter(wallet) } catch (e: Throwable) { @@ -130,6 +141,7 @@ class AdapterFactory( BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne -> { @@ -148,7 +160,7 @@ class AdapterFactory( TronAdapter(tronKitManager.getTronKitWrapper(wallet.account)) } BlockchainType.Ton -> { - TonAdapter(wallet) + TonAdapter(tonKitManager.getTonKitWrapper(wallet.account)) } else -> null @@ -162,6 +174,7 @@ class AdapterFactory( } is TokenType.Bep2 -> getBinanceAdapter(wallet, tokenType.symbol) is TokenType.Spl -> getSplAdapter(wallet, tokenType.address) + is TokenType.Jetton -> getJettonAdapter(wallet, tokenType.address) is TokenType.Unsupported -> null } @@ -199,12 +212,26 @@ class AdapterFactory( return TronTransactionsAdapter(tronKitWrapper, tronTransactionConverter) } + fun tonTransactionsAdapter(source: TransactionSource): ITransactionsAdapter? { + val tonKitWrapper = tonKitManager.getTonKitWrapper(source.account) + val baseToken = coinManager.getToken(TokenQuery(BlockchainType.Ton, TokenType.Native)) ?: return null + val tonTransactionConverter = TonTransactionConverter( + tonKitWrapper.tonKit.receiveAddress, + coinManager, + source, + baseToken + ) + + return TonTransactionsAdapter(tonKitWrapper, tonTransactionConverter) + } + fun unlinkAdapter(wallet: Wallet) { when (val blockchainType = wallet.transactionSource.blockchain.type) { BlockchainType.Ethereum, BlockchainType.BinanceSmartChain, BlockchainType.Polygon, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne -> { val evmKitManager = evmBlockchainManager.getEvmKitManager(blockchainType) evmKitManager.unlink(wallet.account) @@ -218,6 +245,9 @@ class AdapterFactory( BlockchainType.Tron -> { tronKitManager.unlink(wallet.account) } + BlockchainType.Ton -> { + tonKitManager.unlink(wallet.account) + } else -> Unit } } @@ -228,6 +258,7 @@ class AdapterFactory( BlockchainType.BinanceSmartChain, BlockchainType.Polygon, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne -> { val evmKitManager = evmBlockchainManager.getEvmKitManager(blockchainType) evmKitManager.unlink(transactionSource.account) @@ -238,6 +269,9 @@ class AdapterFactory( BlockchainType.Tron -> { tronKitManager.unlink(transactionSource.account) } + BlockchainType.Ton -> { + tonKitManager.unlink(transactionSource.account) + } else -> Unit } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/AdapterManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/AdapterManager.kt index 9b1f3273cf0..7c130d52e4b 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/AdapterManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/AdapterManager.kt @@ -27,7 +27,8 @@ class AdapterManager( private val evmBlockchainManager: EvmBlockchainManager, private val binanceKitManager: BinanceKitManager, private val solanaKitManager: SolanaKitManager, - private val tronKitManager: TronKitManager + private val tronKitManager: TronKitManager, + private val tonKitManager: TonKitManager ) : IAdapterManager, HandlerThread("A") { private val handler: Handler @@ -99,7 +100,7 @@ class AdapterManager( initAdapters(walletManager.activeWallets) } - override fun refresh() { + override suspend fun refresh() { handler.post { adaptersMap.values.forEach { it.refresh() } } @@ -111,6 +112,7 @@ class AdapterManager( binanceKitManager.binanceKit?.refresh() solanaKitManager.solanaKitWrapper?.solanaKit?.refresh() tronKitManager.tronKitWrapper?.tronKit?.refresh() + tonKitManager.tonKitWrapper?.tonKit?.refresh() } @Synchronized diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/BackgroundStateChangeListener.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/BackgroundStateChangeListener.kt deleted file mode 100644 index 1272c6c3492..00000000000 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/BackgroundStateChangeListener.kt +++ /dev/null @@ -1,29 +0,0 @@ -package io.horizontalsystems.bankwallet.core.managers - -import android.app.Activity -import io.horizontalsystems.bankwallet.core.stats.StatsManager -import io.horizontalsystems.bankwallet.modules.keystore.KeyStoreActivity -import io.horizontalsystems.bankwallet.modules.lockscreen.LockScreenActivity -import io.horizontalsystems.core.BackgroundManager -import io.horizontalsystems.core.IPinComponent - -class BackgroundStateChangeListener( - private val pinComponent: IPinComponent, - private val statsManager: StatsManager -) : BackgroundManager.Listener { - - override fun willEnterForeground(activity: Activity) { - pinComponent.willEnterForeground(activity) - - statsManager.sendStats() - } - - override fun didEnterBackground() { - pinComponent.didEnterBackground() - } - - override fun onAllActivitiesDestroyed() { - pinComponent.lock() - } - -} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/BalanceHiddenManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/BalanceHiddenManager.kt index da49f21eb2f..05d419b9326 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/BalanceHiddenManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/BalanceHiddenManager.kt @@ -2,14 +2,18 @@ package io.horizontalsystems.bankwallet.core.managers import io.horizontalsystems.bankwallet.core.ILocalStorage import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch class BalanceHiddenManager( private val localStorage: ILocalStorage, backgroundManager: BackgroundManager, -): BackgroundManager.Listener { +) { val balanceHidden: Boolean get() = localStorage.balanceHidden @@ -20,9 +24,16 @@ class BalanceHiddenManager( private val _balanceHiddenFlow = MutableStateFlow(localStorage.balanceHidden) val balanceHiddenFlow = _balanceHiddenFlow.asStateFlow() + private val scope = CoroutineScope(Dispatchers.Default) init { - backgroundManager.registerListener(this) + scope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterBackground && balanceAutoHide) { + setBalanceHidden(true) + } + } + } if (balanceAutoHide) { setBalanceHidden(true) @@ -42,12 +53,6 @@ class BalanceHiddenManager( } } - override fun didEnterBackground() { - if (balanceAutoHide) { - setBalanceHidden(true) - } - } - private fun setBalanceHidden(hidden: Boolean) { localStorage.balanceHidden = hidden _balanceHiddenFlow.update { localStorage.balanceHidden } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/ConnectivityManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/ConnectivityManager.kt index 222cc653180..c46205e3074 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/ConnectivityManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/ConnectivityManager.kt @@ -7,18 +7,22 @@ import android.net.NetworkCapabilities import android.net.NetworkRequest import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch -class ConnectivityManager(backgroundManager: BackgroundManager) : BackgroundManager.Listener { +class ConnectivityManager(backgroundManager: BackgroundManager) { private val connectivityManager: ConnectivityManager by lazy { App.instance.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager } - + private val scope = CoroutineScope(Dispatchers.Default) private val _networkAvailabilityFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) @@ -32,11 +36,24 @@ class ConnectivityManager(backgroundManager: BackgroundManager) : BackgroundMana private var hasConnection = false init { - backgroundManager.registerListener(this) + scope.launch { + backgroundManager.stateFlow.collect { state -> + when (state) { + BackgroundManagerState.EnterForeground -> { + willEnterForeground() + } + BackgroundManagerState.EnterBackground -> { + didEnterBackground() + } + BackgroundManagerState.AllActivitiesDestroyed -> { + //do nothing + } + } + } + } } - override fun willEnterForeground() { - super.willEnterForeground() + private fun willEnterForeground() { setInitialValues() try { connectivityManager.unregisterNetworkCallback(callback) @@ -46,8 +63,7 @@ class ConnectivityManager(backgroundManager: BackgroundManager) : BackgroundMana connectivityManager.registerNetworkCallback(NetworkRequest.Builder().build(), callback) } - override fun didEnterBackground() { - super.didEnterBackground() + private fun didEnterBackground() { try { connectivityManager.unregisterNetworkCallback(callback) } catch (e: Exception) { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/CurrencyManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/CurrencyManager.kt index 9f40fcd48dd..09cd88a9e84 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/CurrencyManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/CurrencyManager.kt @@ -6,7 +6,6 @@ import io.horizontalsystems.bankwallet.entities.Currency import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -14,7 +13,7 @@ import kotlinx.coroutines.launch class CurrencyManager(private val localStorage: ILocalStorage, private val appConfigProvider: AppConfigProvider) { - private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val scope = CoroutineScope(Dispatchers.Default) private val _baseCurrencyUpdatedFlow: MutableSharedFlow = MutableSharedFlow() val baseCurrencyUpdatedFlow: SharedFlow = _baseCurrencyUpdatedFlow.asSharedFlow() diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmAccountManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmAccountManager.kt index 37b632441a7..ca3fdc5ddf9 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmAccountManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmAccountManager.kt @@ -286,8 +286,8 @@ class EvmAccountManager( accountId = account.id, coinName = tokenInfo.coinName, coinCode = tokenInfo.coinCode, - coinDecimals = tokenInfo.tokenDecimals - + coinDecimals = tokenInfo.tokenDecimals, + coinImage = null ) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmBlockchainManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmBlockchainManager.kt index 162021ca82d..95e132e4c8d 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmBlockchainManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmBlockchainManager.kt @@ -46,6 +46,7 @@ class EvmBlockchainManager( BlockchainType.Polygon -> Chain.Polygon BlockchainType.Avalanche -> Chain.Avalanche BlockchainType.Optimism -> Chain.Optimism + BlockchainType.Base -> Chain.Base BlockchainType.ArbitrumOne -> Chain.ArbitrumOne BlockchainType.Gnosis -> Chain.Gnosis BlockchainType.Fantom -> Chain.Fantom @@ -80,6 +81,7 @@ class EvmBlockchainManager( BlockchainType.ArbitrumOne, BlockchainType.Gnosis, BlockchainType.Fantom, + BlockchainType.Base, ) } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmKitManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmKitManager.kt index 3ea7de6b6c6..1d0ad47838e 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmKitManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmKitManager.kt @@ -2,12 +2,13 @@ package io.horizontalsystems.bankwallet.core.managers import android.os.Handler import android.os.Looper +import android.util.Log import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.bankwallet.core.UnsupportedAccountException -import io.horizontalsystems.bankwallet.core.supportedNftTypes import io.horizontalsystems.bankwallet.entities.Account import io.horizontalsystems.bankwallet.entities.AccountType import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.erc20kit.core.Erc20Kit import io.horizontalsystems.ethereumkit.core.EthereumKit import io.horizontalsystems.ethereumkit.core.signer.Signer @@ -19,7 +20,6 @@ import io.horizontalsystems.ethereumkit.models.RpcSource import io.horizontalsystems.ethereumkit.models.TransactionData import io.horizontalsystems.marketkit.models.BlockchainType import io.horizontalsystems.nftkit.core.NftKit -import io.horizontalsystems.nftkit.models.NftType import io.horizontalsystems.oneinchkit.OneInchKit import io.horizontalsystems.uniswapkit.TokenFactory.UnsupportedChainError import io.horizontalsystems.uniswapkit.UniswapKit @@ -30,20 +30,20 @@ import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.asFlow import java.net.URI class EvmKitManager( val chain: Chain, - backgroundManager: BackgroundManager, + private val backgroundManager: BackgroundManager, private val syncSourceManager: EvmSyncSourceManager -) : BackgroundManager.Listener { +) { private val coroutineScope = CoroutineScope(Dispatchers.Default) + private var job: Job? = null init { - backgroundManager.registerListener(this) - coroutineScope.launch { syncSourceManager.syncSourceObservable.asFlow().collect { blockchain -> handleUpdateNetwork(blockchain) @@ -91,6 +91,7 @@ class EvmKitManager( evmKitWrapper = createKitInstance(accountType, account, blockchainType) useCount = 0 currentAccount = account + subscribeToEvents() } useCount++ @@ -143,24 +144,25 @@ class EvmKitManager( } OneInchKit.addDecorators(evmKit) - var nftKit: NftKit? = null - val supportedNftTypes = blockchainType.supportedNftTypes - if (supportedNftTypes.isNotEmpty()) { - val nftKitInstance = NftKit.getInstance(App.instance, evmKit) - supportedNftTypes.forEach { - when (it) { - NftType.Eip721 -> { - nftKitInstance.addEip721TransactionSyncer() - nftKitInstance.addEip721Decorators() - } - NftType.Eip1155 -> { - nftKitInstance.addEip1155TransactionSyncer() - nftKitInstance.addEip1155Decorators() - } - } - } - nftKit = nftKitInstance - } + val nftKit: NftKit? = null +// var nftKit: NftKit? = null +// val supportedNftTypes = blockchainType.supportedNftTypes +// if (supportedNftTypes.isNotEmpty()) { +// val nftKitInstance = NftKit.getInstance(App.instance, evmKit) +// supportedNftTypes.forEach { +// when (it) { +// NftType.Eip721 -> { +// nftKitInstance.addEip721TransactionSyncer() +// nftKitInstance.addEip721Decorators() +// } +// NftType.Eip1155 -> { +// nftKitInstance.addEip1155TransactionSyncer() +// nftKitInstance.addEip1155Decorators() +// } +// } +// } +// nftKit = nftKitInstance +// } evmKit.start() @@ -173,30 +175,32 @@ class EvmKitManager( useCount -= 1 if (useCount < 1) { + Log.d("AAA", "stopEvmKit()") stopEvmKit() } } } + private fun subscribeToEvents(){ + job = coroutineScope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterForeground) { + evmKitWrapper?.evmKit?.let { kit -> + Handler(Looper.getMainLooper()).postDelayed({ + kit.refresh() + }, 1000) + } + } + } + } + } + private fun stopEvmKit() { + job?.cancel() evmKitWrapper?.evmKit?.stop() evmKitWrapper = null currentAccount = null } - - // - // BackgroundManager.Listener - // - - override fun willEnterForeground() { - this.evmKitWrapper?.evmKit?.let { kit -> - Handler(Looper.getMainLooper()).postDelayed({ - kit.refresh() - }, 1000) - } - } - - override fun didEnterBackground() = Unit } val RpcSource.uris: List diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmSyncSourceManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmSyncSourceManager.kt index 4ff26998bb7..1891db7a065 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmSyncSourceManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/EvmSyncSourceManager.kt @@ -38,6 +38,7 @@ class EvmSyncSourceManager( BlockchainType.ArbitrumOne -> TransactionSource.arbiscan(appConfigProvider.arbiscanApiKey) BlockchainType.Gnosis -> TransactionSource.gnosis(appConfigProvider.gnosisscanApiKey) BlockchainType.Fantom -> TransactionSource.fantom(appConfigProvider.ftmscanApiKey) + BlockchainType.Base -> TransactionSource.basescan(appConfigProvider.basescanApiKey) else -> throw Exception("Non-supported EVM blockchain") } } @@ -131,6 +132,33 @@ class EvmSyncSourceManager( ) ) + BlockchainType.Base -> listOf( + evmSyncSource( + blockchainType, + "Base", + RpcSource.baseRpcHttp(), + defaultTransactionSource(blockchainType) + ), + evmSyncSource( + blockchainType, + "LlamaNodes", + RpcSource.Http( + listOf(URI("https://base.llamarpc.com")), + null + ), + defaultTransactionSource(blockchainType) + ), + evmSyncSource( + blockchainType, + "Omnia", + RpcSource.Http( + listOf(URI("https://endpoints.omniatech.io/v1/base/mainnet/public")), + null + ), + defaultTransactionSource(blockchainType) + ) + ) + BlockchainType.ArbitrumOne -> listOf( evmSyncSource( blockchainType, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/FaqManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/FaqManager.kt index 6b853133838..5c2ad24bf90 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/FaqManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/FaqManager.kt @@ -53,7 +53,7 @@ object FaqManager { val response = OkHttpClient().newCall(request).execute() val listType = object : TypeToken>() {}.type - val list: List = gson.fromJson(response.body.charStream(), listType) + val list: List = gson.fromJson(response.body?.charStream(), listType) response.close() list diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/GuidesManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/GuidesManager.kt index 552f7c2bf90..020af588bd8 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/GuidesManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/GuidesManager.kt @@ -28,7 +28,7 @@ object GuidesManager { .build() val response = OkHttpClient().newCall(request).execute() - val categories = gson.fromJson(response.body.charStream(), Array::class.java) + val categories = gson.fromJson(response.body?.charStream(), Array::class.java) response.close() categories diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NftAdapterManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NftAdapterManager.kt index 4bef4ba2c68..38283e314c6 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NftAdapterManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/NftAdapterManager.kt @@ -46,7 +46,7 @@ class NftAdapterManager( @Synchronized private fun initAdapters(wallets: List) { - val currentAdapters = adaptersMap.toMap() + val currentAdapters = adaptersMap.toMutableMap() adaptersMap.clear() val nftKeys = wallets.map { NftKey(it.account, it.token.blockchainType) }.distinct() @@ -54,22 +54,29 @@ class NftAdapterManager( for (nftKey in nftKeys) { if (nftKey.blockchainType.supportedNftTypes.isEmpty()) continue - val adapter = currentAdapters[nftKey] + val adapter = currentAdapters.remove(nftKey) if (adapter != null) { adaptersMap[nftKey] = adapter } else if (evmBlockchainManager.getBlockchain(nftKey.blockchainType) != null) { - val evmKitWrapper = - evmBlockchainManager.getEvmKitManager(nftKey.blockchainType).getEvmKitWrapper(nftKey.account, nftKey.blockchainType) + val evmKitManager = evmBlockchainManager.getEvmKitManager(nftKey.blockchainType) + val evmKitWrapper = evmKitManager.getEvmKitWrapper(nftKey.account, nftKey.blockchainType) - evmKitWrapper.nftKit?.let { nftKit -> + val nftKit = evmKitWrapper.nftKit + if (nftKit != null) { adaptersMap[nftKey] = EvmNftAdapter(nftKey.blockchainType, nftKit, evmKitWrapper.evmKit.receiveAddress) + } else { + evmKitManager.unlink(nftKey.account) } } else { // Init other blockchain adapter here (e.g. Solana) } } + currentAdapters.forEach { (nftKey, _) -> + evmBlockchainManager.getEvmKitManager(nftKey.blockchainType).unlink(nftKey.account) + } + _adaptersUpdatedFlow.update { adaptersMap.toMap() } } } \ No newline at end of file diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/SolanaKitManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/SolanaKitManager.kt index 53939f271d8..0903090d207 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/SolanaKitManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/SolanaKitManager.kt @@ -8,6 +8,7 @@ import io.horizontalsystems.bankwallet.core.providers.AppConfigProvider import io.horizontalsystems.bankwallet.entities.Account import io.horizontalsystems.bankwallet.entities.AccountType import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.solanakit.Signer import io.horizontalsystems.solanakit.SolanaKit import io.reactivex.Observable @@ -22,10 +23,12 @@ class SolanaKitManager( private val appConfigProvider: AppConfigProvider, private val rpcSourceManager: SolanaRpcSourceManager, private val walletManager: SolanaWalletManager, - backgroundManager: BackgroundManager -) : BackgroundManager.Listener { + private val backgroundManager: BackgroundManager +) { private val coroutineScope = CoroutineScope(Dispatchers.Default) + private var backgroundEventListenerJob: Job? = null + private var rpcUpdatedJob: Job? = null private var tokenAccountJob: Job? = null var solanaKitWrapper: SolanaKitWrapper? = null @@ -41,16 +44,6 @@ class SolanaKitManager( val statusInfo: Map? get() = solanaKitWrapper?.solanaKit?.statusInfo() - init { - backgroundManager.registerListener(this) - - coroutineScope.launch { - rpcSourceManager.rpcSourceUpdateObservable.asFlow().collect { - handleUpdateNetwork() - } - } - } - private fun handleUpdateNetwork() { stopKit() @@ -75,6 +68,7 @@ class SolanaKitManager( else -> throw UnsupportedAccountException() } startKit() + subscribeToEvents() useCount = 0 currentAccount = account } @@ -135,6 +129,8 @@ class SolanaKitManager( solanaKitWrapper = null currentAccount = null tokenAccountJob?.cancel() + backgroundEventListenerJob?.cancel() + rpcUpdatedJob?.cancel() } private fun startKit() { @@ -148,19 +144,25 @@ class SolanaKitManager( } } - // - // BackgroundManager.Listener - // - - override fun willEnterForeground() { - this.solanaKitWrapper?.solanaKit?.let { kit -> - Handler(Looper.getMainLooper()).postDelayed({ - kit.refresh() - }, 1000) + private fun subscribeToEvents() { + backgroundEventListenerJob = coroutineScope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterForeground) { + solanaKitWrapper?.solanaKit?.let { kit -> + Handler(Looper.getMainLooper()).postDelayed({ + kit.refresh() + }, 1000) + } + } + } + } + rpcUpdatedJob = coroutineScope.launch { + rpcSourceManager.rpcSourceUpdateObservable.asFlow().collect { + handleUpdateNetwork() + } } } - override fun didEnterBackground() = Unit } class SolanaKitWrapper(val solanaKit: SolanaKit, val signer: Signer?) \ No newline at end of file diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/SolanaWalletManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/SolanaWalletManager.kt index b6bc9cb0e93..ed8f6b21291 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/SolanaWalletManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/SolanaWalletManager.kt @@ -31,7 +31,8 @@ class SolanaWalletManager( accountId = account.id, coinName = token.coin.name, coinCode = token.coin.code, - coinDecimals = token.decimals + coinDecimals = token.decimals, + coinImage = token.coin.image ) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TonAccountManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TonAccountManager.kt new file mode 100644 index 00000000000..bd865fd0136 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TonAccountManager.kt @@ -0,0 +1,131 @@ +package io.horizontalsystems.bankwallet.core.managers + +import io.horizontalsystems.bankwallet.core.AppLogger +import io.horizontalsystems.bankwallet.core.IAccountManager +import io.horizontalsystems.bankwallet.core.IWalletManager +import io.horizontalsystems.bankwallet.entities.Account +import io.horizontalsystems.bankwallet.entities.AccountOrigin +import io.horizontalsystems.bankwallet.entities.EnabledWallet +import io.horizontalsystems.marketkit.models.BlockchainType +import io.horizontalsystems.marketkit.models.TokenQuery +import io.horizontalsystems.tonkit.models.Event +import io.horizontalsystems.tonkit.models.Jetton +import io.horizontalsystems.tonkit.models.TagQuery +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.launch +import java.util.concurrent.Executors + +class TonAccountManager( + private val accountManager: IAccountManager, + private val walletManager: IWalletManager, + private val tonKitManager: TonKitManager, + private val tokenAutoEnableManager: TokenAutoEnableManager, +) { + private val blockchainType: BlockchainType = BlockchainType.Ton + private val logger = AppLogger("evm-account-manager") + private val singleDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + private val singleDispatcherCoroutineScope = CoroutineScope(singleDispatcher) + private val coroutineScope = CoroutineScope(Dispatchers.IO) + private var transactionSubscriptionJob: Job? = null + + fun start() { + singleDispatcherCoroutineScope.launch { + tonKitManager.kitStartedFlow.collect { started -> + handleStarted(started) + } + } + } + + private suspend fun handleStarted(started: Boolean) { + try { + if (started) { + subscribeToTransactions() + } else { + stop() + } + } catch (exception: Exception) { + logger.warning("error", exception) + } + } + + private fun stop() { + transactionSubscriptionJob?.cancel() + } + + private suspend fun subscribeToTransactions() { + val tonKitWrapper = tonKitManager.tonKitWrapper ?: return + val account = accountManager.activeAccount ?: return + + transactionSubscriptionJob = coroutineScope.launch { + tonKitWrapper.tonKit.eventFlow(TagQuery(null, null, null, null)) + .collect { (events, initial) -> + handle(events, account, tonKitWrapper, initial) + } + } + } + + private fun handle( + events: List, + account: Account, + tonKitWrapper: TonKitWrapper, + initial: Boolean, + ) { + val shouldAutoEnableTokens = tokenAutoEnableManager.isAutoEnabled(account, blockchainType) + + if (initial && account.origin == AccountOrigin.Restored && !account.isWatchAccount && !shouldAutoEnableTokens) { + return + } + + val address = tonKitWrapper.tonKit.receiveAddress + + val jettons = mutableSetOf() + + events.forEach { event -> + event.actions.forEach { action -> + action.jettonTransfer?.let { + if (it.recipient?.address == address) { + jettons.add(it.jetton) + } + } + action.jettonMint?.let { + if (it.recipient.address == address) { + jettons.add(it.jetton) + } + } + action.jettonSwap?.let { + it.jettonMasterIn?.let { + jettons.add(it) + } + } + } + } + + handle(jettons, account) + } + + private fun handle(jettons: Set, account: Account) { + if (jettons.isEmpty()) return + + val existingWallets = walletManager.activeWallets + val existingTokenTypeIds = existingWallets.map { it.token.type.id } + val newJettons = jettons.filter { !existingTokenTypeIds.contains(it.tokenType.id) } + + if (newJettons.isEmpty()) return + + val enabledWallets = newJettons.map { jetton -> + EnabledWallet( + tokenQueryId = TokenQuery(BlockchainType.Ton, jetton.tokenType).id, + accountId = account.id, + coinName = jetton.name, + coinCode = jetton.symbol, + coinDecimals = jetton.decimals, + coinImage = jetton.image + ) + } + + walletManager.saveEnabledWallets(enabledWallets) + } +} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TonKitManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TonKitManager.kt new file mode 100644 index 00000000000..e8fc6a90a05 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TonKitManager.kt @@ -0,0 +1,153 @@ +package io.horizontalsystems.bankwallet.core.managers + +import io.horizontalsystems.bankwallet.core.AdapterState +import io.horizontalsystems.bankwallet.core.App +import io.horizontalsystems.bankwallet.core.UnsupportedAccountException +import io.horizontalsystems.bankwallet.entities.Account +import io.horizontalsystems.bankwallet.entities.AccountType +import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState +import io.horizontalsystems.hdwalletkit.Curve +import io.horizontalsystems.hdwalletkit.HDWallet +import io.horizontalsystems.marketkit.models.TokenType +import io.horizontalsystems.tonkit.core.TonKit +import io.horizontalsystems.tonkit.models.Jetton +import io.horizontalsystems.tonkit.models.Network +import io.horizontalsystems.tonkit.models.SyncState +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch + +class TonKitManager( + private val backgroundManager: BackgroundManager, +) { + private val scope = CoroutineScope(Dispatchers.Default) + private var job: Job? = null + private val _kitStartedFlow = MutableStateFlow(false) + val kitStartedFlow: StateFlow = _kitStartedFlow + + var tonKitWrapper: TonKitWrapper? = null + private set(value) { + field = value + + _kitStartedFlow.update { value != null } + } + + private var useCount = 0 + var currentAccount: Account? = null + private set + + val statusInfo: Map? + get() = tonKitWrapper?.tonKit?.statusInfo() + + @Synchronized + fun getTonKitWrapper(account: Account): TonKitWrapper { + if (this.tonKitWrapper != null && currentAccount != account) { + stop() + } + + if (this.tonKitWrapper == null) { + val accountType = account.type + this.tonKitWrapper = when (accountType) { + is AccountType.Mnemonic -> { + createKitInstance(accountType, account) + } + + is AccountType.TonAddress -> { + createKitInstance(accountType, account) + } + + else -> throw UnsupportedAccountException() + } + scope.launch { + start() + } + useCount = 0 + currentAccount = account + } + + useCount++ + return this.tonKitWrapper!! + } + + private fun createKitInstance( + accountType: AccountType.Mnemonic, + account: Account, + ): TonKitWrapper { + val hdWallet = HDWallet(accountType.seed, 607, HDWallet.Purpose.BIP44, Curve.Ed25519) + val privateKey = hdWallet.privateKey(0) + var privateKeyBytes = privateKey.privKeyBytes + if (privateKeyBytes.size > 32) { + privateKeyBytes = privateKeyBytes.copyOfRange(1, privateKeyBytes.size) + } + val walletType = TonKit.WalletType.Seed(privateKeyBytes) + + val kit = TonKit.getInstance(walletType, Network.MainNet, App.instance, account.id) + + return TonKitWrapper(kit) + } + + private fun createKitInstance( + accountType: AccountType.TonAddress, + account: Account, + ): TonKitWrapper { + val walletType = TonKit.WalletType.Watch(accountType.address) + val kit = TonKit.getInstance(walletType, Network.MainNet, App.instance, account.id) + + return TonKitWrapper(kit) + } + + @Synchronized + fun unlink(account: Account) { + if (account == currentAccount) { + useCount -= 1 + + if (useCount < 1) { + stop() + } + } + } + + private fun stop() { + tonKitWrapper?.tonKit?.stop() + job?.cancel() + tonKitWrapper = null + currentAccount = null + } + + private suspend fun start() { + tonKitWrapper?.tonKit?.start() + job = scope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterForeground) { + tonKitWrapper?.tonKit?.let { kit -> + delay(1000) + kit.refresh() + } + } + } + } + } +} + +class TonKitWrapper(val tonKit: TonKit) + +fun TonKit.statusInfo() = buildMap { + put("Sync State", syncStateFlow.value) + put("Event Sync State", eventSyncStateFlow.value) + put("Jetton Sync State", jettonSyncStateFlow.value) +} + +val Jetton.tokenType + get() = TokenType.Jetton(address.toUserFriendly(true)) + +fun SyncState.toAdapterState(): AdapterState = when (this) { + is SyncState.NotSynced -> AdapterState.NotSynced(error) + is SyncState.Synced -> AdapterState.Synced + is SyncState.Syncing -> AdapterState.Syncing() +} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TransactionAdapterManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TransactionAdapterManager.kt index 881089df4de..bbcae1cb845 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TransactionAdapterManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TransactionAdapterManager.kt @@ -55,6 +55,7 @@ class TransactionAdapterManager( BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne -> { @@ -66,6 +67,9 @@ class TransactionAdapterManager( BlockchainType.Tron -> { adapterFactory.tronTransactionsAdapter(wallet.transactionSource) } + BlockchainType.Ton -> { + adapterFactory.tonTransactionsAdapter(wallet.transactionSource) + } else -> adapter as? ITransactionsAdapter } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TronAccountManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TronAccountManager.kt index 65ae6795471..31875bbc126 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TronAccountManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TronAccountManager.kt @@ -151,7 +151,8 @@ class TronAccountManager( type = foundToken.tokenType, coinName = token.coin.name, coinCode = token.coin.code, - tokenDecimals = token.decimals + tokenDecimals = token.decimals, + coinImage = token.coin.image ) ) } else if (foundToken.tokenInfo != null) { @@ -160,7 +161,8 @@ class TronAccountManager( type = foundToken.tokenType, coinName = foundToken.tokenInfo.tokenName, coinCode = foundToken.tokenInfo.tokenSymbol, - tokenDecimals = foundToken.tokenInfo.tokenDecimal + tokenDecimals = foundToken.tokenInfo.tokenDecimal, + coinImage = null ) ) } @@ -174,7 +176,8 @@ class TronAccountManager( type = tokenType, coinName = token.coin.name, coinCode = token.coin.code, - tokenDecimals = token.decimals + tokenDecimals = token.decimals, + coinImage = token.coin.image ) ) } @@ -221,7 +224,8 @@ class TronAccountManager( accountId = account.id, coinName = tokenInfo.coinName, coinCode = tokenInfo.coinCode, - coinDecimals = tokenInfo.tokenDecimals + coinDecimals = tokenInfo.tokenDecimals, + coinImage = tokenInfo.coinImage ) } @@ -234,7 +238,8 @@ class TronAccountManager( val type: TokenType, val coinName: String, val coinCode: String, - val tokenDecimals: Int + val tokenDecimals: Int, + val coinImage: String?, ) data class FoundToken( diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TronKitManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TronKitManager.kt index 40c097c1f54..b10178194f8 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TronKitManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/TronKitManager.kt @@ -8,18 +8,25 @@ import io.horizontalsystems.bankwallet.core.providers.AppConfigProvider import io.horizontalsystems.bankwallet.entities.Account import io.horizontalsystems.bankwallet.entities.AccountType import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.tronkit.TronKit import io.horizontalsystems.tronkit.models.Address import io.horizontalsystems.tronkit.network.Network import io.horizontalsystems.tronkit.transaction.Signer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch class TronKitManager( private val appConfigProvider: AppConfigProvider, - backgroundManager: BackgroundManager -) : BackgroundManager.Listener { + private val backgroundManager: BackgroundManager +) { + private val scope = CoroutineScope(Dispatchers.Default) + private var job: Job? = null private val network = Network.Mainnet private val _kitStartedFlow = MutableStateFlow(false) val kitStartedFlow: StateFlow = _kitStartedFlow @@ -38,14 +45,10 @@ class TronKitManager( val statusInfo: Map? get() = tronKitWrapper?.tronKit?.statusInfo() - init { - backgroundManager.registerListener(this) - } - @Synchronized fun getTronKitWrapper(account: Account): TronKitWrapper { if (this.tronKitWrapper != null && currentAccount != account) { - stopKit() + stop() } if (this.tronKitWrapper == null) { @@ -61,7 +64,7 @@ class TronKitManager( else -> throw UnsupportedAccountException() } - startKit() + start() useCount = 0 currentAccount = account } @@ -111,34 +114,32 @@ class TronKitManager( useCount -= 1 if (useCount < 1) { - stopKit() + stop() } } } - private fun stopKit() { + private fun stop() { tronKitWrapper?.tronKit?.stop() + job?.cancel() tronKitWrapper = null currentAccount = null } - private fun startKit() { + private fun start() { tronKitWrapper?.tronKit?.start() - } - - // - // BackgroundManager.Listener - // - - override fun willEnterForeground() { - this.tronKitWrapper?.tronKit?.let { kit -> - Handler(Looper.getMainLooper()).postDelayed({ - kit.refresh() - }, 1000) + job = scope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterForeground) { + tronKitWrapper?.tronKit?.let { kit -> + Handler(Looper.getMainLooper()).postDelayed({ + kit.refresh() + }, 1000) + } + } + } } } - - override fun didEnterBackground() = Unit } class TronKitWrapper(val tronKit: TronKit, val signer: Signer?) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/WalletStorage.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/WalletStorage.kt index f68397282ee..29c4f0f21c2 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/WalletStorage.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/WalletStorage.kt @@ -6,6 +6,7 @@ import io.horizontalsystems.bankwallet.core.customCoinUid import io.horizontalsystems.bankwallet.entities.Account import io.horizontalsystems.bankwallet.entities.EnabledWallet import io.horizontalsystems.bankwallet.entities.Wallet +import io.horizontalsystems.marketkit.models.Coin import io.horizontalsystems.marketkit.models.Token import io.horizontalsystems.marketkit.models.TokenQuery @@ -35,7 +36,12 @@ class WalletStorage( val blockchain = blockchains.firstOrNull { it.uid == tokenQuery.blockchainType.uid } ?: return@mapNotNull null val token = Token( - coin = io.horizontalsystems.marketkit.models.Coin(coinUid, enabledWallet.coinName, enabledWallet.coinCode), + coin = Coin( + uid = coinUid, + name = enabledWallet.coinName, + code = enabledWallet.coinCode, + image = enabledWallet.coinImage + ), blockchain = blockchain, type = tokenQuery.tokenType, decimals = enabledWallet.coinDecimals @@ -80,7 +86,8 @@ class WalletStorage( index, wallet.coin.name, wallet.coin.code, - wallet.decimal + wallet.decimal, + wallet.coin.image ) } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/providers/AppConfigProvider.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/providers/AppConfigProvider.kt index ff61d8973fc..5156ddc1dfb 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/providers/AppConfigProvider.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/providers/AppConfigProvider.kt @@ -58,6 +58,9 @@ class AppConfigProvider(localStorage: ILocalStorage) { val ftmscanApiKey by lazy { Translator.getString(R.string.ftmscanApiKey) } + val basescanApiKey by lazy { + Translator.getString(R.string.basescanApiKey) + } val guidesUrl by lazy { Translator.getString(R.string.guidesUrl) } @@ -138,6 +141,7 @@ class AppConfigProvider(localStorage: ILocalStorage) { BlockchainType.Polygon to "0x731352dcF66014156B1560B832B56069e7b38ab1", BlockchainType.Avalanche to "0x731352dcF66014156B1560B832B56069e7b38ab1", BlockchainType.Optimism to "0x731352dcF66014156B1560B832B56069e7b38ab1", + BlockchainType.Base to "0x731352dcF66014156B1560B832B56069e7b38ab1", BlockchainType.ArbitrumOne to "0x731352dcF66014156B1560B832B56069e7b38ab1", BlockchainType.Solana to "ELFQmFXqdS6C1zVqZifs7WAmLKovdEPbWSnqomhZoK3B", BlockchainType.Gnosis to "0x731352dcF66014156B1560B832B56069e7b38ab1", diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/providers/FeeTokenProvider.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/providers/FeeTokenProvider.kt index c99dc58a2dd..1de65013b06 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/providers/FeeTokenProvider.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/providers/FeeTokenProvider.kt @@ -14,7 +14,9 @@ class FeeTokenProvider( val tokenQuery = when (token.type) { is TokenType.Eip20, is TokenType.Spl, - is TokenType.Bep2 -> { + is TokenType.Bep2, + is TokenType.Jetton, + -> { TokenQuery(token.blockchainType, TokenType.Native) } TokenType.Native, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/stats/StatsManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/stats/StatsManager.kt index 4f7d1e7cecc..86ad74054ff 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/stats/StatsManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/stats/StatsManager.kt @@ -30,12 +30,17 @@ import io.horizontalsystems.bankwallet.modules.settings.appearance.PriceChangeIn import io.horizontalsystems.bankwallet.modules.theme.ThemeType import io.horizontalsystems.bankwallet.modules.transactionInfo.options.SpeedUpCancelType import io.horizontalsystems.bankwallet.modules.transactions.FilterTransactionType +import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.core.toHexString import io.horizontalsystems.hdwalletkit.HDExtendedKey import io.horizontalsystems.marketkit.models.HsTimePeriod +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import java.time.Instant import java.util.concurrent.Executors @@ -48,7 +53,21 @@ class StatsManager( private val localStorage: ILocalStorage, private val marketKit: MarketKitWrapper, private val appConfigProvider: AppConfigProvider, + private val backgroundManager: BackgroundManager, ) { + + private val scope = CoroutineScope(Dispatchers.IO) + + init { + scope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterForeground) { + sendStats() + } + } + } + } + var uiStatsEnabled = getInitialUiStatsEnabled() private set diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/storage/AppDatabase.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/storage/AppDatabase.kt index 0807ec30089..55349a45c71 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/storage/AppDatabase.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/storage/AppDatabase.kt @@ -34,6 +34,7 @@ import io.horizontalsystems.bankwallet.core.storage.migrations.Migration_55_56 import io.horizontalsystems.bankwallet.core.storage.migrations.Migration_56_57 import io.horizontalsystems.bankwallet.core.storage.migrations.Migration_57_58 import io.horizontalsystems.bankwallet.core.storage.migrations.Migration_58_59 +import io.horizontalsystems.bankwallet.core.storage.migrations.Migration_59_60 import io.horizontalsystems.bankwallet.entities.ActiveAccount import io.horizontalsystems.bankwallet.entities.BlockchainSettingRecord import io.horizontalsystems.bankwallet.entities.EnabledWallet @@ -59,7 +60,7 @@ import io.horizontalsystems.bankwallet.modules.profeatures.storage.ProFeaturesSe import io.horizontalsystems.bankwallet.modules.walletconnect.storage.WCSessionDao import io.horizontalsystems.bankwallet.modules.walletconnect.storage.WalletConnectV2Session -@Database(version = 59, exportSchema = false, entities = [ +@Database(version = 60, exportSchema = false, entities = [ EnabledWallet::class, EnabledWalletCache::class, AccountRecord::class, @@ -151,7 +152,8 @@ abstract class AppDatabase : RoomDatabase() { Migration_55_56, Migration_56_57, Migration_57_58, - Migration_58_59 + Migration_58_59, + Migration_59_60, ) .build() } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/storage/migrations/Migration_59_60.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/storage/migrations/Migration_59_60.kt new file mode 100644 index 00000000000..662eb939f04 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/storage/migrations/Migration_59_60.kt @@ -0,0 +1,10 @@ +package io.horizontalsystems.bankwallet.core.storage.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +object Migration_59_60 : Migration(59, 60) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE EnabledWallet ADD `coinImage` TEXT") + } +} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/entities/Address.kt b/app/src/main/java/io/horizontalsystems/bankwallet/entities/Address.kt index c19d821c70b..bc397255239 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/entities/Address.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/entities/Address.kt @@ -48,6 +48,7 @@ val BitcoinAddress.tokenType: TokenType BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne, BlockchainType.Solana, BlockchainType.Gnosis, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/entities/EnabledWallet.kt b/app/src/main/java/io/horizontalsystems/bankwallet/entities/EnabledWallet.kt index 718cb3e529c..0b029b37594 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/entities/EnabledWallet.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/entities/EnabledWallet.kt @@ -22,5 +22,6 @@ data class EnabledWallet( val walletOrder: Int? = null, val coinName: String? = null, val coinCode: String? = null, - val coinDecimals: Int? = null + val coinDecimals: Int? = null, + val coinImage: String? ) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/entities/TransactionValue.kt b/app/src/main/java/io/horizontalsystems/bankwallet/entities/TransactionValue.kt index cca176e6c58..bfd4bb41911 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/entities/TransactionValue.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/entities/TransactionValue.kt @@ -1,5 +1,6 @@ package io.horizontalsystems.bankwallet.entities +import io.horizontalsystems.bankwallet.R import io.horizontalsystems.bankwallet.core.alternativeImageUrl import io.horizontalsystems.bankwallet.core.badge import io.horizontalsystems.bankwallet.core.iconPlaceholder @@ -28,6 +29,33 @@ sealed class TransactionValue { open val nftUid: NftUid? = null + data class JettonValue( + val name: String, + val symbol: String, + override val decimals: Int, + val value: BigDecimal, + val image: String? + ) : TransactionValue() { + override val fullName = name + override val coinUid = symbol + override val coinCode = symbol + override val coin = null + override val badge = "JETTON" + override val coinIconUrl = image + override val alternativeCoinIconUrl = null + override val coinIconPlaceholder = R.drawable.the_open_network_jetton + override val decimalValue = value + override val zeroValue: Boolean + get() = value.compareTo(BigDecimal.ZERO) == 0 + override val isMaxValue: Boolean + get() = value.isMaxValue(decimals) + override val abs: TransactionValue + get() = copy(value = value.abs()) + override val formattedString: String + get() = "n/a" + + } + data class CoinValue(val token: Token, val value: BigDecimal) : TransactionValue() { override val coin: Coin = token.coin override val badge: String? = token.badge diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/entities/transactionrecords/evm/EvmTransactionRecord.kt b/app/src/main/java/io/horizontalsystems/bankwallet/entities/transactionrecords/evm/EvmTransactionRecord.kt index 39ef7b7154a..31502170813 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/entities/transactionrecords/evm/EvmTransactionRecord.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/entities/transactionrecords/evm/EvmTransactionRecord.kt @@ -84,6 +84,7 @@ open class EvmTransactionRecord( is TransactionValue.RawValue -> value is TransactionValue.NftValue -> value.copy(value = totalValue) + is TransactionValue.JettonValue -> value } if (totalValue > BigDecimal.ZERO) { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/AddressHandlerFactory.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/AddressHandlerFactory.kt index 93adcf99650..46827df2304 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/AddressHandlerFactory.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/AddressHandlerFactory.kt @@ -59,6 +59,7 @@ class AddressHandlerFactory( BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne -> { @@ -92,6 +93,7 @@ class AddressHandlerFactory( BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne -> { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/AddressInputModule.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/AddressInputModule.kt index 099aa025b4a..8b788e512d9 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/AddressInputModule.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/AddressInputModule.kt @@ -32,6 +32,7 @@ object AddressInputModule { BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne -> { @@ -83,6 +84,7 @@ object AddressInputModule { BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne -> { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/IAddressHandler.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/IAddressHandler.kt index 2aef02484d9..8da0d710195 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/IAddressHandler.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/address/IAddressHandler.kt @@ -13,7 +13,7 @@ import io.horizontalsystems.ethereumkit.core.AddressValidator import io.horizontalsystems.marketkit.models.BlockchainType import io.horizontalsystems.marketkit.models.TokenQuery import io.horizontalsystems.marketkit.models.TokenType -import io.horizontalsystems.tonkit.TonKit +import io.horizontalsystems.tonkit.core.TonKit import io.horizontalsystems.tronkit.account.AddressHandler import org.web3j.ens.EnsResolver @@ -109,6 +109,7 @@ class AddressHandlerUdn( BlockchainType.BinanceChain, BlockchainType.Polygon, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Avalanche, BlockchainType.Gnosis, BlockchainType.Fantom, @@ -138,6 +139,7 @@ class AddressHandlerUdn( BlockchainType.Avalanche -> "AVAX" BlockchainType.Ethereum, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne, BlockchainType.Gnosis, BlockchainType.Fantom -> "ERC20" @@ -290,7 +292,7 @@ class AddressHandlerTon : IAddressHandler { override val blockchainType = BlockchainType.Ton override fun isSupported(value: String) = try { - TonKit.validate(value) + TonKit.validateAddress(value) true } catch (e: Exception) { false diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddBep2TokenBlockchainService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddBep2TokenBlockchainService.kt index 5a90c75ac66..5f5d45b89eb 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddBep2TokenBlockchainService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddBep2TokenBlockchainService.kt @@ -3,7 +3,12 @@ package io.horizontalsystems.bankwallet.modules.addtoken import io.horizontalsystems.bankwallet.core.INetworkManager import io.horizontalsystems.bankwallet.core.customCoinUid import io.horizontalsystems.bankwallet.modules.addtoken.AddTokenModule.IAddTokenBlockchainService -import io.horizontalsystems.marketkit.models.* +import io.horizontalsystems.marketkit.models.Blockchain +import io.horizontalsystems.marketkit.models.BlockchainType +import io.horizontalsystems.marketkit.models.Coin +import io.horizontalsystems.marketkit.models.Token +import io.horizontalsystems.marketkit.models.TokenQuery +import io.horizontalsystems.marketkit.models.TokenType class AddBep2TokenBlockchainService( private val blockchain: Blockchain, @@ -26,7 +31,11 @@ class AddBep2TokenBlockchainService( ?: throw AddTokenService.TokenError.NotFound val tokenQuery = tokenQuery(reference) return Token( - coin = Coin(tokenQuery.customCoinUid, tokenInfo.name, tokenInfo.original_symbol), + coin = Coin( + uid = tokenQuery.customCoinUid, + name = tokenInfo.name, + code = tokenInfo.original_symbol + ), blockchain = blockchain, type = tokenQuery.tokenType, decimals = 0 diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddEvmTokenBlockchainService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddEvmTokenBlockchainService.kt index 63ec361bd35..62e35c3f561 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddEvmTokenBlockchainService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddEvmTokenBlockchainService.kt @@ -7,7 +7,11 @@ import io.horizontalsystems.erc20kit.core.Eip20Provider import io.horizontalsystems.ethereumkit.core.AddressValidator import io.horizontalsystems.ethereumkit.models.Address import io.horizontalsystems.ethereumkit.models.RpcSource -import io.horizontalsystems.marketkit.models.* +import io.horizontalsystems.marketkit.models.Blockchain +import io.horizontalsystems.marketkit.models.Coin +import io.horizontalsystems.marketkit.models.Token +import io.horizontalsystems.marketkit.models.TokenQuery +import io.horizontalsystems.marketkit.models.TokenType import kotlinx.coroutines.rx2.await class AddEvmTokenBlockchainService( @@ -32,7 +36,11 @@ class AddEvmTokenBlockchainService( val tokenInfo = eip20Provider.getTokenInfo(Address(reference)).await() val tokenQuery = tokenQuery(reference) return Token( - coin = Coin(tokenQuery.customCoinUid, tokenInfo.tokenName, tokenInfo.tokenSymbol, tokenInfo.tokenDecimal), + coin = Coin( + uid = tokenQuery.customCoinUid, + name = tokenInfo.tokenName, + code = tokenInfo.tokenSymbol + ), blockchain = blockchain, type = tokenQuery.tokenType, decimals = tokenInfo.tokenDecimal diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddSolanaTokenBlockchainService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddSolanaTokenBlockchainService.kt index 2f407df94be..6ae2d4a5fc5 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddSolanaTokenBlockchainService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddSolanaTokenBlockchainService.kt @@ -33,7 +33,11 @@ class AddSolanaTokenBlockchainService( val tokenInfo = tokenProvider.getTokenInfo(reference) val tokenQuery = tokenQuery(reference) return Token( - coin = Coin(tokenQuery.customCoinUid, tokenInfo.name, tokenInfo.symbol, tokenInfo.decimals), + coin = Coin( + uid = tokenQuery.customCoinUid, + name = tokenInfo.name, + code = tokenInfo.symbol + ), blockchain = blockchain, type = tokenQuery.tokenType, decimals = tokenInfo.decimals diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTokenFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTokenFragment.kt index 7d74f7e56a7..02b96784018 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTokenFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTokenFragment.kt @@ -1,16 +1,13 @@ package io.horizontalsystems.bankwallet.modules.addtoken import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Icon +import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -38,9 +35,11 @@ import io.horizontalsystems.bankwallet.ui.compose.components.AppBar import io.horizontalsystems.bankwallet.ui.compose.components.CellUniversalLawrenceSection import io.horizontalsystems.bankwallet.ui.compose.components.FormsInput import io.horizontalsystems.bankwallet.ui.compose.components.FormsInputStateWarning +import io.horizontalsystems.bankwallet.ui.compose.components.HSpacer import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.MenuItem import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.body_leah import io.horizontalsystems.bankwallet.ui.compose.components.subhead1_grey import io.horizontalsystems.core.SnackbarDuration @@ -116,26 +115,30 @@ private fun AddTokenScreen( } } - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = stringResource(R.string.AddToken_Title), - navigationIcon = { - HsBackButton(onClick = closeScreen) - }, - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Button_Add), - onClick = viewModel::onAddClick, - enabled = uiState.addButtonEnabled + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.AddToken_Title), + navigationIcon = { + HsBackButton(onClick = closeScreen) + }, + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Button_Add), + onClick = viewModel::onAddClick, + enabled = uiState.addButtonEnabled + ) ) ) - ) + }, + ) { innerPaddings -> Column( modifier = Modifier - .weight(1f) + .padding(innerPaddings) .verticalScroll(rememberScrollState()) ) { - Spacer(modifier = Modifier.height(12.dp)) + VSpacer(12.dp) CellUniversalLawrenceSection( listOf { @@ -149,7 +152,7 @@ private fun AddTokenScreen( painter = painterResource(R.drawable.ic_blocks_24), contentDescription = null ) - Spacer(modifier = Modifier.width(16.dp)) + HSpacer(16.dp) body_leah( text = stringResource(R.string.AddToken_Blockchain), modifier = Modifier.weight(1f) @@ -167,7 +170,7 @@ private fun AddTokenScreen( } ) - Spacer(modifier = Modifier.height(32.dp)) + VSpacer(32.dp) FormsInput( modifier = Modifier.padding(horizontal = 16.dp), @@ -179,7 +182,7 @@ private fun AddTokenScreen( viewModel.onEnterText(it) } - Spacer(modifier = Modifier.height(32.dp)) + VSpacer(32.dp) uiState.tokenInfo?.let { tokenInfo -> CellUniversalLawrenceSection( diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTokenService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTokenService.kt index caef99814b1..e05fcd145ca 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTokenService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTokenService.kt @@ -26,6 +26,7 @@ class AddTokenService( BlockchainType.Ethereum, BlockchainType.BinanceSmartChain, BlockchainType.Tron, + BlockchainType.Ton, BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.BinanceChain, @@ -33,6 +34,7 @@ class AddTokenService( BlockchainType.Fantom, BlockchainType.ArbitrumOne, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Solana ) @@ -53,6 +55,9 @@ class AddTokenService( BlockchainType.Tron -> { AddTronTokenBlockchainService.getInstance(blockchain) } + BlockchainType.Ton -> { + AddTonTokenBlockchainService(blockchain) + } BlockchainType.Solana -> { AddSolanaTokenBlockchainService.getInstance(blockchain) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTonTokenBlockchainService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTonTokenBlockchainService.kt new file mode 100644 index 00000000000..d4fa3391c07 --- /dev/null +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTonTokenBlockchainService.kt @@ -0,0 +1,45 @@ +package io.horizontalsystems.bankwallet.modules.addtoken + +import io.horizontalsystems.bankwallet.core.customCoinUid +import io.horizontalsystems.marketkit.models.Blockchain +import io.horizontalsystems.marketkit.models.BlockchainType +import io.horizontalsystems.marketkit.models.Coin +import io.horizontalsystems.marketkit.models.Token +import io.horizontalsystems.marketkit.models.TokenQuery +import io.horizontalsystems.marketkit.models.TokenType +import io.horizontalsystems.tonkit.Address +import io.horizontalsystems.tonkit.core.TonKit +import io.horizontalsystems.tonkit.models.Network + +class AddTonTokenBlockchainService(private val blockchain: Blockchain) : AddTokenModule.IAddTokenBlockchainService { + override fun isValid(reference: String) = try { + TonKit.validateAddress(reference) + true + } catch (e: Throwable) { + false + } + + override fun tokenQuery(reference: String): TokenQuery { + return TokenQuery(BlockchainType.Ton, TokenType.Jetton(reference)) + } + + override suspend fun token(reference: String): Token { + val jetton = TonKit.getJetton(Network.MainNet, Address.parse(reference)) + + val tokenQuery = tokenQuery(reference) + return Token( + coin = Coin( + uid = tokenQuery.customCoinUid, + name = jetton.name, + code = jetton.symbol, + image = jetton.image + ), + blockchain = blockchain, + type = tokenQuery.tokenType, + decimals = jetton.decimals + ) + + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTronTokenBlockchainService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTronTokenBlockchainService.kt index 0583ccb6b13..89814e69333 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTronTokenBlockchainService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/addtoken/AddTronTokenBlockchainService.kt @@ -34,7 +34,11 @@ class AddTronTokenBlockchainService( val tokenInfo = trc20Provider.getTokenInfo(Address.fromBase58(reference)) val tokenQuery = tokenQuery(reference) return Token( - coin = Coin(tokenQuery.customCoinUid, tokenInfo.tokenName, tokenInfo.tokenSymbol, tokenInfo.tokenDecimal), + coin = Coin( + uid = tokenQuery.customCoinUid, + name = tokenInfo.tokenName, + code = tokenInfo.tokenSymbol + ), blockchain = blockchain, type = tokenQuery.tokenType, decimals = tokenInfo.tokenDecimal diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/backuplocal/fullbackup/BackupProvider.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/backuplocal/fullbackup/BackupProvider.kt index 37b09ad4fb2..2ee15e0e97c 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/backuplocal/fullbackup/BackupProvider.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/backuplocal/fullbackup/BackupProvider.kt @@ -150,7 +150,8 @@ class BackupProvider( accountId = account.id, coinName = it.coinName, coinCode = it.coinCode, - coinDecimals = it.decimals + coinDecimals = it.decimals, + coinImage = null ) } walletManager.saveEnabledWallets(enabledWallets) @@ -180,7 +181,8 @@ class BackupProvider( accountId = account.id, coinName = it.coinName, coinCode = it.coinCode, - coinDecimals = it.decimals + coinDecimals = it.decimals, + coinImage = null ) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceAdapterRepository.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceAdapterRepository.kt index 4b61c6b9908..56469134dfc 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceAdapterRepository.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceAdapterRepository.kt @@ -116,7 +116,7 @@ class BalanceAdapterRepository( return null } - fun refresh() { + suspend fun refresh() { adapterManager.refresh() } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceService.kt index 24a1718863d..311d7bfe40d 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceService.kt @@ -178,7 +178,7 @@ class BalanceService( sortAndEmitItems() } - fun refresh() { + suspend fun refresh() { xRateRepository.refresh() adapterRepository.refresh() } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceViewItemFactory.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceViewItemFactory.kt index c0eeb4d8dcb..2843f5e517f 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceViewItemFactory.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceViewItemFactory.kt @@ -96,6 +96,7 @@ class BalanceViewItemFactory { BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Solana, BlockchainType.Gnosis, BlockchainType.Fantom, @@ -207,7 +208,24 @@ class BalanceViewItemFactory { LockedValue( title = TranslatableString.ResString(R.string.Balance_LockedAmount_Title), infoTitle = TranslatableString.ResString(R.string.Info_LockTime_Title), - info = TranslatableString.ResString(R.string.Info_LockTime_Description_Static), + info = TranslatableString.ResString(R.string.Info_ProcessingBalance_Description), + coinValue = it + ) + ) + } + + lockedCoinValue( + state, + item.balanceData.pending, + hideBalance, + wallet.decimal, + wallet.token + )?.let { + add( + LockedValue( + title = TranslatableString.ResString(R.string.Balance_ProcessingBalance_Title), + infoTitle = TranslatableString.ResString(R.string.Info_ProcessingBalance_Title), + info = TranslatableString.ResString(R.string.Info_ProcessingBalance_Description), coinValue = it ) ) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceViewModel.kt index 28cf5bdcbb3..c11d19b657c 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/BalanceViewModel.kt @@ -182,7 +182,7 @@ class BalanceViewModel( stat(page = StatPage.Balance, event = StatEvent.Refresh) - viewModelScope.launch { + viewModelScope.launch(Dispatchers.Default) { isRefreshing = true emitState() diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/ui/BalanceForAccount.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/ui/BalanceForAccount.kt index 9cdea92a938..d85d30f2ef7 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/ui/BalanceForAccount.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/balance/ui/BalanceForAccount.kt @@ -6,14 +6,16 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.Crossfade import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -119,56 +121,75 @@ fun BalanceForAccount(navController: NavController, accountViewItem: AccountView ) } ) { - Column { - AppBar( - title = { - BalanceTitleRow(navController, accountViewItem.name) - }, - menuItems = buildList { - if (accountViewItem.type.supportsWalletConnect) { - add( - MenuItem( - title = TranslatableString.ResString(R.string.WalletConnect_NewConnect), - icon = R.drawable.ic_qr_scan_20, - onClick = { - when (val state = viewModel.getWalletConnectSupportState()) { - WCManager.SupportState.Supported -> { - qrScannerLauncher.launch(QRScannerActivity.getScanQrIntent(context, true)) - - stat(page = StatPage.Balance, event = StatEvent.Open(StatPage.ScanQrCode)) - } - - WCManager.SupportState.NotSupportedDueToNoActiveAccount -> { - navController.slideFromBottom(R.id.wcErrorNoAccountFragment) - } - - is WCManager.SupportState.NotSupportedDueToNonBackedUpAccount -> { - val text = Translator.getString(R.string.WalletConnect_Error_NeedBackup) - navController.slideFromBottom( - R.id.backupRequiredDialog, - BackupRequiredDialog.Input(state.account, text) - ) - - stat(page = StatPage.Balance, event = StatEvent.Open(StatPage.BackupRequired)) - } - - is WCManager.SupportState.NotSupported -> { - navController.slideFromBottom( - R.id.wcAccountTypeNotSupportedDialog, - WCAccountTypeNotSupportedDialog.Input(state.accountTypeDescription) - ) + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = { + BalanceTitleRow(navController, accountViewItem.name) + }, + menuItems = buildList { + if (accountViewItem.type.supportsWalletConnect) { + add( + MenuItem( + title = TranslatableString.ResString(R.string.WalletConnect_NewConnect), + icon = R.drawable.ic_qr_scan_20, + onClick = { + when (val state = + viewModel.getWalletConnectSupportState()) { + WCManager.SupportState.Supported -> { + qrScannerLauncher.launch( + QRScannerActivity.getScanQrIntent(context, true) + ) + + stat( + page = StatPage.Balance, + event = StatEvent.Open(StatPage.ScanQrCode) + ) + } + + WCManager.SupportState.NotSupportedDueToNoActiveAccount -> { + navController.slideFromBottom(R.id.wcErrorNoAccountFragment) + } + + is WCManager.SupportState.NotSupportedDueToNonBackedUpAccount -> { + val text = + Translator.getString(R.string.WalletConnect_Error_NeedBackup) + navController.slideFromBottom( + R.id.backupRequiredDialog, + BackupRequiredDialog.Input(state.account, text) + ) + + stat( + page = StatPage.Balance, + event = StatEvent.Open(StatPage.BackupRequired) + ) + } + + is WCManager.SupportState.NotSupported -> { + navController.slideFromBottom( + R.id.wcAccountTypeNotSupportedDialog, + WCAccountTypeNotSupportedDialog.Input(state.accountTypeDescription) + ) + } } } - } + ) ) - ) + } } - } - ) - + ) + } + ) { paddingValues -> val uiState = viewModel.uiState - Crossfade(uiState.viewState, label = "") { viewState -> + Crossfade( + targetState = uiState.viewState, + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + label = "" + ) { viewState -> when (viewState) { ViewState.Success -> { val balanceViewItems = uiState.balanceViewItems @@ -178,7 +199,7 @@ fun BalanceForAccount(navController: NavController, accountViewItem: AccountView accountViewItem, navController, uiState, - viewModel.totalUiState + viewModel.totalUiState, ) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/basecurrency/BaseCurrencySettingsFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/basecurrency/BaseCurrencySettingsFragment.kt index 3490de4ffce..6fe5c91dcdd 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/basecurrency/BaseCurrencySettingsFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/basecurrency/BaseCurrencySettingsFragment.kt @@ -1,8 +1,23 @@ package io.horizontalsystems.bankwallet.modules.basecurrency -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.* -import androidx.compose.material.* +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.Icon +import androidx.compose.material.ModalBottomSheetLayout +import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold +import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope @@ -18,7 +33,17 @@ import androidx.navigation.NavController import io.horizontalsystems.bankwallet.R import io.horizontalsystems.bankwallet.core.BaseComposeFragment import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme -import io.horizontalsystems.bankwallet.ui.compose.components.* +import io.horizontalsystems.bankwallet.ui.compose.components.AppBar +import io.horizontalsystems.bankwallet.ui.compose.components.ButtonPrimaryTransparent +import io.horizontalsystems.bankwallet.ui.compose.components.ButtonPrimaryYellow +import io.horizontalsystems.bankwallet.ui.compose.components.CellUniversalLawrenceSection +import io.horizontalsystems.bankwallet.ui.compose.components.HeaderText +import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton +import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal +import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantWarning +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer +import io.horizontalsystems.bankwallet.ui.compose.components.body_leah +import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_grey import io.horizontalsystems.bankwallet.ui.extensions.BottomSheetHeader import kotlinx.coroutines.launch @@ -80,29 +105,32 @@ private fun BaseCurrencyScreen( ) } ) { - Column( - modifier = Modifier.background(color = ComposeAppTheme.colors.tyler) - ) { - AppBar( - title = stringResource(R.string.SettingsCurrency_Title), - navigationIcon = { - HsBackButton(onClick = { navController.popBackStack() }) - } - ) + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.SettingsCurrency_Title), + navigationIcon = { + HsBackButton(onClick = { navController.popBackStack() }) + } + ) + } + ) { paddingValues -> Column( - Modifier.verticalScroll(rememberScrollState()) + Modifier + .verticalScroll(rememberScrollState()) + .padding(paddingValues) ) { - Spacer(Modifier.height(12.dp)) + VSpacer(12.dp) CellUniversalLawrenceSection(viewModel.popularItems) { item -> CurrencyCell( item.currency.code, item.currency.symbol, item.currency.flag, - item.selected, - { viewModel.onSelectBaseCurrency(item.currency) } - ) + item.selected + ) { viewModel.onSelectBaseCurrency(item.currency) } } - Spacer(Modifier.height(24.dp)) + VSpacer(24.dp) HeaderText( stringResource(R.string.SettingsCurrency_Other) ) @@ -111,11 +139,10 @@ private fun BaseCurrencyScreen( item.currency.code, item.currency.symbol, item.currency.flag, - item.selected, - { viewModel.onSelectBaseCurrency(item.currency) } - ) + item.selected + ) { viewModel.onSelectBaseCurrency(item.currency) } } - Spacer(Modifier.height(24.dp)) + VSpacer(24.dp) } } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/chart/ChartIndicatorManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/chart/ChartIndicatorManager.kt index a6749d0a20e..325cdd40bbf 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/chart/ChartIndicatorManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/chart/ChartIndicatorManager.kt @@ -4,7 +4,6 @@ import io.horizontalsystems.bankwallet.core.ILocalStorage import io.horizontalsystems.chartview.models.ChartIndicator import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.launch @@ -14,7 +13,7 @@ class ChartIndicatorManager( private val chartIndicatorSettingsDao: ChartIndicatorSettingsDao, private val localStorage: ILocalStorage ) { - private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val scope = CoroutineScope(Dispatchers.Default) val isEnabled: Boolean get() = localStorage.chartIndicatorsEnabled private val _isEnabledFlow : MutableSharedFlow = MutableSharedFlow() diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/CoinFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/CoinFragment.kt index bdb54b9bd3c..f967bc02cef 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/CoinFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/CoinFragment.kt @@ -2,10 +2,11 @@ package io.horizontalsystems.bankwallet.modules.coin import android.os.Parcelable import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -92,114 +93,128 @@ fun CoinTabs( val coroutineScope = rememberCoroutineScope() val view = LocalView.current - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = viewModel.fullCoin.coin.code, - navigationIcon = { - HsBackButton(onClick = { navController.popBackStack() }) - }, - menuItems = buildList { - if (viewModel.isWatchlistEnabled) { - if (viewModel.isFavorite) { - add( - MenuItem( - title = TranslatableString.ResString(R.string.CoinPage_Unfavorite), - icon = R.drawable.ic_filled_star_24, - tint = ComposeAppTheme.colors.jacob, - onClick = { - viewModel.onUnfavoriteClick() - - stat(page = StatPage.CoinPage, event = StatEvent.RemoveFromWatchlist(viewModel.fullCoin.coin.uid)) - } + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = viewModel.fullCoin.coin.code, + navigationIcon = { + HsBackButton(onClick = { navController.popBackStack() }) + }, + menuItems = buildList { + if (viewModel.isWatchlistEnabled) { + if (viewModel.isFavorite) { + add( + MenuItem( + title = TranslatableString.ResString(R.string.CoinPage_Unfavorite), + icon = R.drawable.ic_filled_star_24, + tint = ComposeAppTheme.colors.jacob, + onClick = { + viewModel.onUnfavoriteClick() + + stat( + page = StatPage.CoinPage, + event = StatEvent.RemoveFromWatchlist(viewModel.fullCoin.coin.uid) + ) + } + ) ) - ) - } else { - add( - MenuItem( - title = TranslatableString.ResString(R.string.CoinPage_Favorite), - icon = R.drawable.ic_star_24, - onClick = { - viewModel.onFavoriteClick() - - stat(page = StatPage.CoinPage, event = StatEvent.AddToWatchlist(viewModel.fullCoin.coin.uid)) - } + } else { + add( + MenuItem( + title = TranslatableString.ResString(R.string.CoinPage_Favorite), + icon = R.drawable.ic_star_24, + onClick = { + viewModel.onFavoriteClick() + + stat( + page = StatPage.CoinPage, + event = StatEvent.AddToWatchlist(viewModel.fullCoin.coin.uid) + ) + } + ) ) - ) + } } } - } - ) - - val selectedTab = tabs[pagerState.currentPage] - val tabItems = tabs.map { - TabItem(stringResource(id = it.titleResId), it == selectedTab, it) + ) } - Tabs(tabItems, onClick = { tab -> - coroutineScope.launch { - pagerState.scrollToPage(tab.ordinal) + ) { innerPaddings -> + Column( + modifier = Modifier.padding(innerPaddings) + ) { + val selectedTab = tabs[pagerState.currentPage] + val tabItems = tabs.map { + TabItem(stringResource(id = it.titleResId), it == selectedTab, it) + } + Tabs(tabItems, onClick = { tab -> + coroutineScope.launch { + pagerState.scrollToPage(tab.ordinal) - stat(page = StatPage.CoinPage, event = StatEvent.SwitchTab(tab.statTab)) + stat(page = StatPage.CoinPage, event = StatEvent.SwitchTab(tab.statTab)) - if (tab == CoinModule.Tab.Details && viewModel.shouldShowSubscriptionInfo()) { - viewModel.subscriptionInfoShown() + if (tab == CoinModule.Tab.Details && viewModel.shouldShowSubscriptionInfo()) { + viewModel.subscriptionInfoShown() - delay(1000) - navController.slideFromBottom(R.id.subscriptionInfoFragment) - } - } - }) - - HorizontalPager( - state = pagerState, - userScrollEnabled = false - ) { page -> - when (tabs[page]) { - CoinModule.Tab.Overview -> { - CoinOverviewScreen( - fullCoin = viewModel.fullCoin, - navController = navController - ) + delay(1000) + navController.slideFromBottom(R.id.subscriptionInfoFragment) + } } + }) + + HorizontalPager( + state = pagerState, + userScrollEnabled = false + ) { page -> + when (tabs[page]) { + CoinModule.Tab.Overview -> { + CoinOverviewScreen( + fullCoin = viewModel.fullCoin, + navController = navController + ) + } - CoinModule.Tab.Market -> { - CoinMarketsScreen(fullCoin = viewModel.fullCoin) - } + CoinModule.Tab.Market -> { + CoinMarketsScreen(fullCoin = viewModel.fullCoin) + } - CoinModule.Tab.Details -> { - CoinAnalyticsScreen( - fullCoin = viewModel.fullCoin, - navController = navController, - fragmentManager = fragmentManager - ) + CoinModule.Tab.Details -> { + CoinAnalyticsScreen( + fullCoin = viewModel.fullCoin, + navController = navController, + fragmentManager = fragmentManager + ) + } } -// CoinModule.Tab.Tweets -> { -// CoinTweetsScreen(fullCoin = viewModel.fullCoin) -// } } - } - viewModel.successMessage?.let { - HudHelper.showSuccessMessage(view, it) + viewModel.successMessage?.let { + HudHelper.showSuccessMessage(view, it) - viewModel.onSuccessMessageShown() + viewModel.onSuccessMessageShown() + } } } } @Composable fun CoinNotFound(coinUid: String, navController: NavController) { - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = coinUid, - navigationIcon = { - HsBackButton(onClick = { navController.popBackStack() }) - } - ) - - ListEmptyView( - text = stringResource(R.string.CoinPage_CoinNotFound, coinUid), - icon = R.drawable.ic_not_available - ) - - } + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = coinUid, + navigationIcon = { + HsBackButton(onClick = { navController.popBackStack() }) + } + ) + }, + content = { + ListEmptyView( + paddingValues = it, + text = stringResource(R.string.CoinPage_CoinNotFound, coinUid), + icon = R.drawable.ic_not_available + ) + } + ) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/coinmarkets/CoinMarketsService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/coinmarkets/CoinMarketsService.kt index 585dabb7aae..0ffca460389 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/coinmarkets/CoinMarketsService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/coinmarkets/CoinMarketsService.kt @@ -9,7 +9,6 @@ import io.horizontalsystems.marketkit.models.MarketTicker import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.await @@ -19,7 +18,7 @@ class CoinMarketsService( private val currencyManager: CurrencyManager, private val marketKit: MarketKitWrapper, ) { - private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + private val coroutineScope = CoroutineScope(Dispatchers.IO) private var marketTickers = listOf() private var verifiedType: VerifiedType = VerifiedType.All diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/investments/CoinInvestmentsFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/investments/CoinInvestmentsFragment.kt index 5ffb7805970..d735bbd4e91 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/investments/CoinInvestmentsFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/investments/CoinInvestmentsFragment.kt @@ -3,9 +3,7 @@ package io.horizontalsystems.bankwallet.modules.coin.investments import android.os.Parcelable import androidx.compose.animation.Crossfade import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -14,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -78,50 +77,64 @@ private fun CoinInvestmentsScreen( val isRefreshing by viewModel.isRefreshingLiveData.observeAsState(false) val viewItems by viewModel.viewItemsLiveData.observeAsState() - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = stringResource(R.string.CoinPage_FundsInvested), - navigationIcon = { - HsBackButton(onClick = onClickNavigation) - } - ) - + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.CoinPage_FundsInvested), + navigationIcon = { + HsBackButton(onClick = onClickNavigation) + } + ) + } + ) { innerPadding -> HSSwipeRefresh( refreshing = isRefreshing, - onRefresh = viewModel::refresh - ) { - Crossfade(viewState) { viewState -> - when (viewState) { - ViewState.Loading -> { - Loading() - } + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + onRefresh = viewModel::refresh, + content = { + Crossfade(viewState, label = "") { viewState -> + when (viewState) { + ViewState.Loading -> { + Loading() + } - is ViewState.Error -> { - ListErrorView(stringResource(R.string.SyncError), viewModel::onErrorClick) - } + is ViewState.Error -> { + ListErrorView( + stringResource(R.string.SyncError), + viewModel::onErrorClick + ) + } - ViewState.Success -> { - LazyColumn(modifier = Modifier.fillMaxSize()) { - viewItems?.forEach { viewItem -> - item { - CoinInvestmentHeader(viewItem.amount, viewItem.info) + ViewState.Success -> { + LazyColumn(modifier = Modifier.fillMaxSize()) { + viewItems?.forEach { viewItem -> + item { + CoinInvestmentHeader(viewItem.amount, viewItem.info) - Spacer(modifier = Modifier.height(12.dp)) - } - item { - CellSingleLineLawrenceSection(viewItem.fundViewItems) { fundViewItem -> - CoinInvestmentFund(fundViewItem) { onClickFundUrl(fundViewItem.url) } + Spacer(modifier = Modifier.height(12.dp)) + } + item { + CellSingleLineLawrenceSection(viewItem.fundViewItems) { fundViewItem -> + CoinInvestmentFund(fundViewItem) { + onClickFundUrl( + fundViewItem.url + ) + } + } + Spacer(modifier = Modifier.height(24.dp)) } - Spacer(modifier = Modifier.height(24.dp)) } } } - } - null -> {} + null -> {} + } } } - } + ) } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/overview/CoinOverviewViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/overview/CoinOverviewViewModel.kt index e374259b2b3..8b7143234f9 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/overview/CoinOverviewViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/overview/CoinOverviewViewModel.kt @@ -16,6 +16,7 @@ import io.horizontalsystems.bankwallet.core.bitcoinCashCoinType import io.horizontalsystems.bankwallet.core.eip20TokenUrl import io.horizontalsystems.bankwallet.core.imageUrl import io.horizontalsystems.bankwallet.core.isSupported +import io.horizontalsystems.bankwallet.core.jettonUrl import io.horizontalsystems.bankwallet.core.order import io.horizontalsystems.bankwallet.core.providers.Translator import io.horizontalsystems.bankwallet.core.shorten @@ -154,6 +155,22 @@ class CoinOverviewViewModel( && token.blockchainType.supports(accountTypeNotWatch) when (val tokenType = token.type) { + is TokenType.Jetton -> { + val inWallet = + canAddToWallet && activeWallets.any { it.token == token } + items.add( + TokenVariant( + value = tokenType.address.shorten(), + copyValue = tokenType.address, + imgUrl = token.blockchainType.imageUrl, + explorerUrl = token.blockchain.jettonUrl(tokenType.address), + name = token.blockchain.name, + token = token, + canAddToWallet = canAddToWallet, + inWallet = inWallet + ) + ) + } is TokenType.Eip20 -> { val inWallet = canAddToWallet && activeWallets.any { it.token == token } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/ranks/CoinRankFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/ranks/CoinRankFragment.kt index 75135201970..5f7ecd473b0 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/ranks/CoinRankFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/ranks/CoinRankFragment.kt @@ -3,7 +3,6 @@ package io.horizontalsystems.bankwallet.modules.coin.ranks import androidx.compose.animation.Crossfade import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer @@ -19,6 +18,7 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Divider +import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -105,17 +105,25 @@ private fun CoinRankScreen( val uiState = viewModel.uiState val viewItems = viewModel.uiState.rankViewItems - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Button_Close), - icon = R.drawable.ic_close, - onClick = { navController.popBackStack() } + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Button_Close), + icon = R.drawable.ic_close, + onClick = { navController.popBackStack() } + ) ) ) - ) - Crossfade(uiState.viewState, label = "") { viewItemState -> + } + ) { padding -> + Crossfade( + targetState = uiState.viewState, + modifier = Modifier.padding(padding), + label = "" + ) { viewItemState -> when (viewItemState) { ViewState.Loading -> { Loading() diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/reports/CoinReportsFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/reports/CoinReportsFragment.kt index 5dea81fb864..046c2c1cc21 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/reports/CoinReportsFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/reports/CoinReportsFragment.kt @@ -2,13 +2,13 @@ package io.horizontalsystems.bankwallet.modules.coin.reports import android.os.Parcelable import androidx.compose.animation.Crossfade -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -62,51 +62,62 @@ private fun CoinReportsScreen( val isRefreshing by viewModel.isRefreshingLiveData.observeAsState(false) val reportViewItems by viewModel.reportViewItemsLiveData.observeAsState() - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = stringResource(R.string.CoinPage_Reports), - navigationIcon = { - HsBackButton(onClick = onClickNavigation) - } - ) + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.CoinPage_Reports), + navigationIcon = { + HsBackButton(onClick = onClickNavigation) + } + ) + } + ) { padding -> HSSwipeRefresh( refreshing = isRefreshing, - onRefresh = viewModel::refresh - ) { - Crossfade(viewState) { viewState -> - when (viewState) { - ViewState.Loading -> { - Loading() - } + modifier = Modifier + .padding(padding) + .fillMaxSize(), + onRefresh = viewModel::refresh, + content = { + Crossfade(viewState, label = "") { viewState -> + when (viewState) { + ViewState.Loading -> { + Loading() + } - is ViewState.Error -> { - ListErrorView(stringResource(R.string.SyncError), viewModel::onErrorClick) - } + is ViewState.Error -> { + ListErrorView( + stringResource(R.string.SyncError), + viewModel::onErrorClick + ) + } - ViewState.Success -> { - LazyColumn(modifier = Modifier.fillMaxSize()) { - reportViewItems?.let { - items(it) { report -> - Spacer(modifier = Modifier.height(12.dp)) - CellNews( - source = report.author, - title = report.title, - body = report.body, - date = report.date, - ) { - onClickReportUrl(report.url) + ViewState.Success -> { + LazyColumn(modifier = Modifier.fillMaxSize()) { + reportViewItems?.let { + items(it) { report -> + Spacer(modifier = Modifier.height(12.dp)) + CellNews( + source = report.author, + title = report.title, + body = report.body, + date = report.date, + ) { + onClickReportUrl(report.url) + } + } + item { + Spacer(modifier = Modifier.height(12.dp)) } - } - item { - Spacer(modifier = Modifier.height(12.dp)) } } } - } - null -> {} + null -> {} + } } } - } + ) } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/treasuries/CoinTreasuriesFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/treasuries/CoinTreasuriesFragment.kt index 8ba8c6b920b..64eccffc850 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/treasuries/CoinTreasuriesFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/coin/treasuries/CoinTreasuriesFragment.kt @@ -1,18 +1,17 @@ package io.horizontalsystems.bankwallet.modules.coin.treasuries import androidx.compose.animation.Crossfade -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -43,6 +42,7 @@ import io.horizontalsystems.bankwallet.ui.compose.components.ListErrorView import io.horizontalsystems.bankwallet.ui.compose.components.MarketCoinFirstRow import io.horizontalsystems.bankwallet.ui.compose.components.SectionItemBorderedRowUniversalClear import io.horizontalsystems.bankwallet.ui.compose.components.SortMenu +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_grey import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_jacob @@ -64,87 +64,101 @@ class CoinTreasuriesFragment : BaseComposeFragment() { val viewState by viewModel.viewStateLiveData.observeAsState() val isRefreshing by viewModel.isRefreshingLiveData.observeAsState(false) val treasuriesData by viewModel.coinTreasuriesLiveData.observeAsState() - val chainSelectorDialogState by viewModel.treasuryTypeSelectorDialogStateLiveData.observeAsState(TvlModule.SelectorDialogState.Closed) + val chainSelectorDialogState by viewModel.treasuryTypeSelectorDialogStateLiveData.observeAsState( + TvlModule.SelectorDialogState.Closed + ) - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = stringResource(R.string.CoinPage_Treasuries), - navigationIcon = { - HsBackButton(onClick = { findNavController().popBackStack() }) - } - ) + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.CoinPage_Treasuries), + navigationIcon = { + HsBackButton(onClick = { findNavController().popBackStack() }) + } + ) + } + ) { paddingValues -> HSSwipeRefresh( refreshing = isRefreshing, + modifier = Modifier.padding(paddingValues), onRefresh = { viewModel.refresh() - } - ) { - Crossfade(viewState) { viewState -> - when (viewState) { - ViewState.Loading -> { - Loading() - } - - is ViewState.Error -> { - ListErrorView(stringResource(R.string.SyncError), viewModel::onErrorClick) - } + }, + content = { + Crossfade(viewState, label = "") { viewState -> + when (viewState) { + ViewState.Loading -> { + Loading() + } - ViewState.Success -> { - LazyColumn(modifier = Modifier.fillMaxSize()) { - treasuriesData?.let { treasuriesData -> - item { - CoinTreasuriesMenu( - treasuryTypeSelect = treasuriesData.treasuryTypeSelect, - sortDescending = treasuriesData.sortDescending, - onClickTreasuryTypeSelector = viewModel::onClickTreasuryTypeSelector, - onToggleSortType = viewModel::onToggleSortType - ) - } + is ViewState.Error -> { + ListErrorView( + stringResource(R.string.SyncError), + viewModel::onErrorClick + ) + } - items(treasuriesData.coinTreasuries) { item -> - SectionItemBorderedRowUniversalClear( - borderBottom = true - ) { - HsImage( - url = item.fundLogoUrl, - modifier = Modifier - .padding(end = 16.dp) - .size(32.dp) + ViewState.Success -> { + LazyColumn(modifier = Modifier.fillMaxSize()) { + treasuriesData?.let { treasuriesData -> + item { + CoinTreasuriesMenu( + treasuryTypeSelect = treasuriesData.treasuryTypeSelect, + sortDescending = treasuriesData.sortDescending, + onClickTreasuryTypeSelector = viewModel::onClickTreasuryTypeSelector, + onToggleSortType = viewModel::onToggleSortType ) - Column( - modifier = Modifier.fillMaxWidth() + } + + items(treasuriesData.coinTreasuries) { item -> + SectionItemBorderedRowUniversalClear( + borderBottom = true ) { - MarketCoinFirstRow(item.fund, item.amount) - Spacer(modifier = Modifier.height(3.dp)) - CoinTreasurySecondRow(item.country, item.amountInCurrency) + HsImage( + url = item.fundLogoUrl, + modifier = Modifier + .padding(end = 16.dp) + .size(32.dp) + ) + Column( + modifier = Modifier.fillMaxWidth() + ) { + MarketCoinFirstRow(item.fund, item.amount) + VSpacer(3.dp) + CoinTreasurySecondRow( + item.country, + item.amountInCurrency + ) + } } } - } - item { - Spacer(modifier = Modifier.height(32.dp)) - CellFooter(text = stringResource(id = R.string.CoinPage_Treasuries_PoweredBy)) + item { + VSpacer(32.dp) + CellFooter(text = stringResource(id = R.string.CoinPage_Treasuries_PoweredBy)) + } } } } - } - null -> {} + null -> {} + } } - } - // chain selector dialog - when (val option = chainSelectorDialogState) { - is CoinTreasuriesModule.SelectorDialogState.Opened -> { - AlertGroup( - R.string.CoinPage_Treasuries_FilterTitle, - option.select, - viewModel::onSelectTreasuryType, - viewModel::onTreasuryTypeSelectorDialogDismiss - ) + // chain selector dialog + when (val option = chainSelectorDialogState) { + is CoinTreasuriesModule.SelectorDialogState.Opened -> { + AlertGroup( + R.string.CoinPage_Treasuries_FilterTitle, + option.select, + viewModel::onSelectTreasuryType, + viewModel::onTreasuryTypeSelectorDialogDismiss + ) + } } } - } + ) } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/configuredtoken/ConfiguredTokenInfoViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/configuredtoken/ConfiguredTokenInfoViewModel.kt index f6ff5fcca4b..bfcffb3feba 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/configuredtoken/ConfiguredTokenInfoViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/configuredtoken/ConfiguredTokenInfoViewModel.kt @@ -9,6 +9,7 @@ import io.horizontalsystems.bankwallet.core.bep2TokenUrl import io.horizontalsystems.bankwallet.core.eip20TokenUrl import io.horizontalsystems.bankwallet.core.iconPlaceholder import io.horizontalsystems.bankwallet.core.imageUrl +import io.horizontalsystems.bankwallet.core.jettonUrl import io.horizontalsystems.bankwallet.core.managers.RestoreSettingsManager import io.horizontalsystems.bankwallet.modules.market.ImageSource import io.horizontalsystems.marketkit.models.BlockchainType @@ -34,6 +35,9 @@ class ConfiguredTokenInfoViewModel( is TokenType.Spl -> { ConfiguredTokenInfoType.Contract(type.address, token.blockchain.type.imageUrl, token.blockchain.eip20TokenUrl(type.address)) } + is TokenType.Jetton -> { + ConfiguredTokenInfoType.Contract(type.address, token.blockchain.type.imageUrl, token.blockchain.jettonUrl(type.address)) + } is TokenType.Derived -> { ConfiguredTokenInfoType.Bips(token.blockchain.name) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/AddressScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/AddressScreen.kt index 01cb8853589..dbb2ae703e6 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/AddressScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/AddressScreen.kt @@ -1,6 +1,5 @@ package io.horizontalsystems.bankwallet.modules.contacts.screen -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -11,6 +10,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope @@ -31,6 +31,7 @@ import io.horizontalsystems.bankwallet.ui.compose.components.FormsInput import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.MenuItem import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.body_lucian import io.horizontalsystems.bankwallet.ui.compose.components.subhead1_leah import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_grey @@ -71,74 +72,79 @@ fun AddressScreen( ) } ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(color = ComposeAppTheme.colors.tyler) - ) { - AppBar( - title = uiState.headerTitle.getString(), - navigationIcon = { - HsBackButton(onNavigateToBack) - }, - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Button_Done), - enabled = uiState.doneEnabled, - onClick = { - uiState.addressState?.dataOrNull?.let { - onDone(ContactAddress(uiState.blockchain, it.hex)) + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = uiState.headerTitle.getString(), + navigationIcon = { + HsBackButton(onNavigateToBack) + }, + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Button_Done), + enabled = uiState.doneEnabled, + onClick = { + uiState.addressState?.dataOrNull?.let { + onDone(ContactAddress(uiState.blockchain, it.hex)) + } } - } + ) ) ) - ) - - Spacer(modifier = Modifier.height(12.dp)) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + Spacer(modifier = Modifier.height(12.dp)) - CellUniversalLawrenceSection( - listOf { - RowUniversal( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - onClick = if (uiState.canChangeBlockchain) onNavigateToBlockchainSelector else null - ) { - subhead2_grey( - text = stringResource(R.string.AddToken_Blockchain), - modifier = Modifier.weight(1f) - ) - subhead1_leah( - text = uiState.blockchain.name, - modifier = Modifier.padding(horizontal = 8.dp) - ) - if (uiState.canChangeBlockchain) { - Icon( - painter = painterResource(id = R.drawable.ic_down_arrow_20), - contentDescription = null, - tint = ComposeAppTheme.colors.grey + CellUniversalLawrenceSection( + listOf { + RowUniversal( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + onClick = if (uiState.canChangeBlockchain) onNavigateToBlockchainSelector else null + ) { + subhead2_grey( + text = stringResource(R.string.AddToken_Blockchain), + modifier = Modifier.weight(1f) + ) + subhead1_leah( + text = uiState.blockchain.name, + modifier = Modifier.padding(horizontal = 8.dp) ) + if (uiState.canChangeBlockchain) { + Icon( + painter = painterResource(id = R.drawable.ic_down_arrow_20), + contentDescription = null, + tint = ComposeAppTheme.colors.grey + ) + } } } - } - ) + ) - Spacer(modifier = Modifier.height(32.dp)) + VSpacer(32.dp) - FormsInput( - modifier = Modifier.padding(horizontal = 16.dp), - initial = uiState.address, - hint = stringResource(R.string.Contacts_AddressHint), - state = uiState.addressState, - qrScannerEnabled = true, - ) { - viewModel.onEnterAddress(it) - } - if (uiState.showDelete) { - Spacer(modifier = Modifier.height(32.dp)) - DeleteAddressButton { - coroutineScope.launch { - modalBottomSheetState.show() + FormsInput( + modifier = Modifier.padding(horizontal = 16.dp), + initial = uiState.address, + hint = stringResource(R.string.Contacts_AddressHint), + state = uiState.addressState, + qrScannerEnabled = true, + ) { + viewModel.onEnterAddress(it) + } + if (uiState.showDelete) { + VSpacer(32.dp) + DeleteAddressButton { + coroutineScope.launch { + modalBottomSheetState.show() + } } } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/ContactScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/ContactScreen.kt index 1743a9dc144..4d50b72f6ab 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/ContactScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/ContactScreen.kt @@ -2,10 +2,8 @@ package io.horizontalsystems.bankwallet.modules.contacts.screen import androidx.activity.compose.BackHandler import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -17,6 +15,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -56,6 +55,7 @@ import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.MenuItem import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantWarning +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.body_jacob import io.horizontalsystems.bankwallet.ui.compose.components.body_leah import io.horizontalsystems.bankwallet.ui.compose.components.body_lucian @@ -155,29 +155,32 @@ fun ContactScreen( } } - Column( - modifier = Modifier - .fillMaxSize() - .background(color = ComposeAppTheme.colors.tyler) - ) { - AppBar( - title = uiState.headerTitle.getString(), - navigationIcon = { - HsBackButton { - confirmNavigateToBack() - } - }, - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Button_Save), - enabled = uiState.saveEnabled, - onClick = viewModel::onSave + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = uiState.headerTitle.getString(), + navigationIcon = { + HsBackButton { + confirmNavigateToBack() + } + }, + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Button_Save), + enabled = uiState.saveEnabled, + onClick = viewModel::onSave + ) ) ) - ) - - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - Spacer(Modifier.height(12.dp)) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + ) { + VSpacer(12.dp) FormsInput( modifier = Modifier .focusRequester(focusRequester) @@ -210,7 +213,7 @@ fun ContactScreen( } ) - Spacer(Modifier.height(32.dp)) + VSpacer(32.dp) } } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/ContactsScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/ContactsScreen.kt index e6b2671adf5..95311667ba8 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/ContactsScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/contacts/screen/ContactsScreen.kt @@ -4,10 +4,9 @@ import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.Image -import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -17,6 +16,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -48,6 +48,7 @@ import io.horizontalsystems.bankwallet.ui.compose.components.ScreenMessageWithAc import io.horizontalsystems.bankwallet.ui.compose.components.SearchBar import io.horizontalsystems.bankwallet.ui.compose.components.SelectorDialogCompose import io.horizontalsystems.bankwallet.ui.compose.components.SelectorItem +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.body_leah import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_grey import io.horizontalsystems.core.SnackbarDuration @@ -166,112 +167,120 @@ fun ContactsScreen( } ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(color = ComposeAppTheme.colors.tyler) - ) { - SearchBar( - title = stringResource(R.string.Contacts), - searchHintText = stringResource(R.string.Market_Search_Hint), - menuItems = buildList { - if (uiState.showAddContact) { - add( - MenuItem( - title = TranslatableString.ResString(R.string.Contacts_NewContact), - icon = R.drawable.icon_user_plus, - tint = ComposeAppTheme.colors.jacob, - onClick = onNavigateToCreateContact + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + SearchBar( + title = stringResource(R.string.Contacts), + searchHintText = stringResource(R.string.Market_Search_Hint), + menuItems = buildList { + if (uiState.showAddContact) { + add( + MenuItem( + title = TranslatableString.ResString(R.string.Contacts_NewContact), + icon = R.drawable.icon_user_plus, + tint = ComposeAppTheme.colors.jacob, + onClick = onNavigateToCreateContact + ) ) - ) - } - if (uiState.showMoreOptions) { - add( - MenuItem( - title = TranslatableString.ResString(R.string.Contacts_ActionMore), - icon = R.drawable.ic_more2_20, - tint = ComposeAppTheme.colors.jacob, - enabled = true, - onClick = { - showMoreSelectorDialog = true - } + } + if (uiState.showMoreOptions) { + add( + MenuItem( + title = TranslatableString.ResString(R.string.Contacts_ActionMore), + icon = R.drawable.ic_more2_20, + tint = ComposeAppTheme.colors.jacob, + enabled = true, + onClick = { + showMoreSelectorDialog = true + } + ) ) - ) + } + }, + onClose = onNavigateToBack, + onSearchTextChanged = { text -> + viewModel.onEnterQuery(text) } - }, - onClose = onNavigateToBack, - onSearchTextChanged = { text -> - viewModel.onEnterQuery(text) - } - ) - if (uiState.contacts.isNotEmpty()) { - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - Spacer(Modifier.height(12.dp)) - CellUniversalLawrenceSection(uiState.contacts) { contact -> - Contact(contact) { - if (viewModel.shouldShowReplaceWarning(contact)) { - coroutineScope.launch { - bottomSheetType = ContactsScreenBottomSheetType.ReplaceAddressConfirmation - selectedContact = contact - bottomSheetState.show() + ) + } + ) { paddingValues -> + Box( + modifier = Modifier + .fillMaxWidth() + .padding(paddingValues) + ) { + if (uiState.contacts.isNotEmpty()) { + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + VSpacer(12.dp) + CellUniversalLawrenceSection(uiState.contacts) { contact -> + Contact(contact) { + if (viewModel.shouldShowReplaceWarning(contact)) { + coroutineScope.launch { + bottomSheetType = + ContactsScreenBottomSheetType.ReplaceAddressConfirmation + selectedContact = contact + bottomSheetState.show() + } + } else { + onNavigateToContact(contact) } - } else { - onNavigateToContact(contact) } } + VSpacer(32.dp) } - Spacer(Modifier.height(32.dp)) - } - } else { - if (uiState.searchMode) { - ListEmptyView( - text = stringResource(R.string.EmptyResults), - icon = R.drawable.ic_not_found - ) } else { - ScreenMessageWithAction( - text = stringResource(R.string.Contacts_NoContacts), - icon = R.drawable.icon_user_plus - ) { - ButtonPrimaryYellow( - modifier = Modifier - .padding(horizontal = 48.dp) - .fillMaxWidth(), - title = stringResource(R.string.Contacts_AddNewContact), - onClick = onNavigateToCreateContact + if (uiState.searchMode) { + ListEmptyView( + text = stringResource(R.string.EmptyResults), + icon = R.drawable.ic_not_found ) + } else { + ScreenMessageWithAction( + text = stringResource(R.string.Contacts_NoContacts), + icon = R.drawable.icon_user_plus + ) { + ButtonPrimaryYellow( + modifier = Modifier + .padding(horizontal = 48.dp) + .fillMaxWidth(), + title = stringResource(R.string.Contacts_AddNewContact), + onClick = onNavigateToCreateContact + ) + } } } - } - if (showMoreSelectorDialog) { - SelectorDialogCompose( - title = stringResource(R.string.Contacts_ActionMore), - items = ContactsModule.ContactsAction.values().map { - (SelectorItem(stringResource(it.title), false, it)) - }, - onDismissRequest = { - showMoreSelectorDialog = false - }, - onSelectItem = { action -> - when (action) { - ContactsModule.ContactsAction.Restore -> { - if (viewModel.shouldShowRestoreWarning()) { - coroutineScope.launch { - bottomSheetType = ContactsScreenBottomSheetType.RestoreContactsConfirmation - bottomSheetState.show() + if (showMoreSelectorDialog) { + SelectorDialogCompose( + title = stringResource(R.string.Contacts_ActionMore), + items = ContactsModule.ContactsAction.values().map { + (SelectorItem(stringResource(it.title), false, it)) + }, + onDismissRequest = { + showMoreSelectorDialog = false + }, + onSelectItem = { action -> + when (action) { + ContactsModule.ContactsAction.Restore -> { + if (viewModel.shouldShowRestoreWarning()) { + coroutineScope.launch { + bottomSheetType = + ContactsScreenBottomSheetType.RestoreContactsConfirmation + bottomSheetState.show() + } + } else { + restoreLauncher.launch(arrayOf("application/json")) } - } else { - restoreLauncher.launch(arrayOf("application/json")) } - } - ContactsModule.ContactsAction.Backup -> { - App.pinComponent.keepUnlocked() - backupLauncher.launch(viewModel.backupFileName) + ContactsModule.ContactsAction.Backup -> { + App.pinComponent.keepUnlocked() + backupLauncher.launch(viewModel.backupFileName) + } } - } - }) + }) + } } } } @@ -291,7 +300,12 @@ fun Contact( text = contact.name, maxLines = 1 ) - subhead2_grey(text = stringResource(R.string.Contacts_AddressesCount, contact.addresses.size)) + subhead2_grey( + text = stringResource( + R.string.Contacts_AddressesCount, + contact.addresses.size + ) + ) } Image( modifier = Modifier.size(20.dp), diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/main/MainFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/main/MainFragment.kt index aac43b1d06c..51fcefd1bf2 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/main/MainFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/main/MainFragment.kt @@ -158,83 +158,87 @@ private fun MainScreen( ) }, ) { - Box(Modifier.fillMaxSize()) { - Scaffold( - backgroundColor = ComposeAppTheme.colors.tyler, - bottomBar = { - Column { - if (uiState.torEnabled) { - TorStatusView() - } - HsBottomNavigation( - backgroundColor = ComposeAppTheme.colors.tyler, - elevation = 10.dp - ) { - uiState.mainNavItems.forEach { item -> - HsBottomNavigationItem( - icon = { - BadgedIcon(item.badge) { - Icon( - painter = painterResource(item.mainNavItem.iconRes), - contentDescription = stringResource(item.mainNavItem.titleRes) - ) - } - }, - selected = item.selected, - enabled = item.enabled, - selectedContentColor = ComposeAppTheme.colors.jacob, - unselectedContentColor = if (item.enabled) ComposeAppTheme.colors.grey else ComposeAppTheme.colors.grey50, - onClick = { - viewModel.onSelect(item.mainNavItem) + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + bottomBar = { + Column { + if (uiState.torEnabled) { + TorStatusView() + } + HsBottomNavigation( + backgroundColor = ComposeAppTheme.colors.tyler, + elevation = 10.dp + ) { + uiState.mainNavItems.forEach { item -> + HsBottomNavigationItem( + icon = { + BadgedIcon(item.badge) { + Icon( + painter = painterResource(item.mainNavItem.iconRes), + contentDescription = stringResource(item.mainNavItem.titleRes) + ) + } + }, + selected = item.selected, + enabled = item.enabled, + selectedContentColor = ComposeAppTheme.colors.jacob, + unselectedContentColor = if (item.enabled) ComposeAppTheme.colors.grey else ComposeAppTheme.colors.grey50, + onClick = { + viewModel.onSelect(item.mainNavItem) - stat(page = StatPage.Main, event = StatEvent.SwitchTab(item.mainNavItem.statTab)) - }, - onLongClick = { - if (item.mainNavItem == MainNavigation.Balance) { - coroutineScope.launch { - modalBottomSheetState.show() + stat( + page = StatPage.Main, + event = StatEvent.SwitchTab(item.mainNavItem.statTab) + ) + }, + onLongClick = { + if (item.mainNavItem == MainNavigation.Balance) { + coroutineScope.launch { + modalBottomSheetState.show() - stat(page = StatPage.Main, event = StatEvent.Open(StatPage.SwitchWallet)) - } + stat( + page = StatPage.Main, + event = StatEvent.Open(StatPage.SwitchWallet) + ) } } - ) - } + } + ) } } } - ) { - BackHandler(enabled = modalBottomSheetState.isVisible) { - coroutineScope.launch { - modalBottomSheetState.hide() - } + } + ) { + BackHandler(enabled = modalBottomSheetState.isVisible) { + coroutineScope.launch { + modalBottomSheetState.hide() } - Column(modifier = Modifier.padding(it)) { - LaunchedEffect(key1 = selectedPage, block = { - pagerState.scrollToPage(selectedPage) - }) + } + Column(modifier = Modifier.padding(it)) { + LaunchedEffect(key1 = selectedPage, block = { + pagerState.scrollToPage(selectedPage) + }) - HorizontalPager( - modifier = Modifier.weight(1f), - state = pagerState, - userScrollEnabled = false, - verticalAlignment = Alignment.Top - ) { page -> - when (uiState.mainNavItems[page].mainNavItem) { - MainNavigation.Market -> MarketScreen(fragmentNavController) - MainNavigation.Balance -> BalanceScreen(fragmentNavController) - MainNavigation.Transactions -> TransactionsScreen( - fragmentNavController, - transactionsViewModel - ) + HorizontalPager( + modifier = Modifier.weight(1f), + state = pagerState, + userScrollEnabled = false, + verticalAlignment = Alignment.Top + ) { page -> + when (uiState.mainNavItems[page].mainNavItem) { + MainNavigation.Market -> MarketScreen(fragmentNavController) + MainNavigation.Balance -> BalanceScreen(fragmentNavController) + MainNavigation.Transactions -> TransactionsScreen( + fragmentNavController, + transactionsViewModel + ) - MainNavigation.Settings -> SettingsScreen(fragmentNavController) - } + MainNavigation.Settings -> SettingsScreen(fragmentNavController) } } } - HideContentBox(uiState.contentHidden) } + HideContentBox(uiState.contentHidden) } if (uiState.showWhatsNew) { @@ -309,7 +313,10 @@ private fun HideContentBox(contentHidden: Boolean) { } else { Modifier } - Box(Modifier.fillMaxSize().then(backgroundModifier)) + Box( + Modifier + .fillMaxSize() + .then(backgroundModifier)) } @Composable diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/evmprivatekey/EvmPrivateKeyFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/evmprivatekey/EvmPrivateKeyFragment.kt index 03eb160288b..3b1aa6ef8e0 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/evmprivatekey/EvmPrivateKeyFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/evmprivatekey/EvmPrivateKeyFragment.kt @@ -1,17 +1,15 @@ package io.horizontalsystems.bankwallet.modules.manageaccount.evmprivatekey import android.os.Parcelable -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope @@ -37,6 +35,7 @@ import io.horizontalsystems.bankwallet.ui.compose.components.AppBar import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.MenuItem import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantWarning +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.helpers.TextHelper import io.horizontalsystems.core.helpers.HudHelper import kotlinx.coroutines.launch @@ -91,44 +90,57 @@ private fun EvmPrivateKeyScreen( ) } ) { - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = stringResource(R.string.EvmPrivateKey_Title), - navigationIcon = { - HsBackButton(onClick = navController::popBackStack) - }, - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Info_Title), - icon = R.drawable.ic_info_24, - onClick = { - FaqManager.showFaqPage(navController, FaqManager.faqPathPrivateKeys) - - stat(page = StatPage.EvmPrivateKey, event = StatEvent.Open(StatPage.Info)) - } + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.EvmPrivateKey_Title), + navigationIcon = { + HsBackButton(onClick = navController::popBackStack) + }, + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Info_Title), + icon = R.drawable.ic_info_24, + onClick = { + FaqManager.showFaqPage(navController, FaqManager.faqPathPrivateKeys) + stat( + page = StatPage.EvmPrivateKey, + event = StatEvent.Open(StatPage.Info) + ) + } + ) ) ) - ) - + } + ) { paddingValues -> Column( - modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.Top + modifier = Modifier.padding(paddingValues), ) { - Spacer(Modifier.height(12.dp)) - TextImportantWarning( - modifier = Modifier.padding(horizontal = 16.dp), - text = stringResource(R.string.PrivateKeys_NeverShareWarning) - ) - Spacer(Modifier.height(24.dp)) - HidableContent(evmPrivateKey, stringResource(R.string.EvmPrivateKey_ShowPrivateKey)) { - stat(page = StatPage.EvmPrivateKey, event = StatEvent.ToggleHidden) + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Top + ) { + VSpacer(12.dp) + TextImportantWarning( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.PrivateKeys_NeverShareWarning) + ) + VSpacer(24.dp) + HidableContent( + evmPrivateKey, + stringResource(R.string.EvmPrivateKey_ShowPrivateKey) + ) { + stat(page = StatPage.EvmPrivateKey, event = StatEvent.ToggleHidden) + } } - } - ActionButton(R.string.Alert_Copy) { - coroutineScope.launch { - sheetState.show() + + ActionButton(R.string.Alert_Copy) { + coroutineScope.launch { + sheetState.show() + } } } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/recoveryphrase/RecoveryPhraseFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/recoveryphrase/RecoveryPhraseFragment.kt index fb771291495..6e63e520d32 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/recoveryphrase/RecoveryPhraseFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/recoveryphrase/RecoveryPhraseFragment.kt @@ -1,16 +1,13 @@ package io.horizontalsystems.bankwallet.modules.manageaccount.recoveryphrase -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -43,6 +40,7 @@ import io.horizontalsystems.bankwallet.ui.compose.components.AppBar import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.MenuItem import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantWarning +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.helpers.TextHelper import io.horizontalsystems.core.helpers.HudHelper import kotlinx.coroutines.launch @@ -95,49 +93,56 @@ private fun RecoveryPhraseScreen( ) } ) { - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = stringResource(R.string.RecoveryPhrase_Title), - navigationIcon = { - HsBackButton(onClick = navController::popBackStack) - }, - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Info_Title), - icon = R.drawable.ic_info_24, - onClick = { - FaqManager.showFaqPage(navController, FaqManager.faqPathPrivateKeys) - - stat(page = StatPage.RecoveryPhrase, event = StatEvent.Open(StatPage.Info)) - } + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.RecoveryPhrase_Title), + navigationIcon = { + HsBackButton(onClick = navController::popBackStack) + }, + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Info_Title), + icon = R.drawable.ic_info_24, + onClick = { + FaqManager.showFaqPage(navController, FaqManager.faqPathPrivateKeys) + stat( + page = StatPage.RecoveryPhrase, + event = StatEvent.Open(StatPage.Info) + ) + } + ) ) ) - ) - + } + ) { paddingValues -> Column( - modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.Top + modifier = Modifier.padding(paddingValues), ) { - Spacer(Modifier.height(12.dp)) - TextImportantWarning( - modifier = Modifier.padding(horizontal = 16.dp), - text = stringResource(R.string.PrivateKeys_NeverShareWarning) - ) - Spacer(Modifier.height(24.dp)) - var hidden by remember { mutableStateOf(true) } - SeedPhraseList(viewModel.wordsNumbered, hidden) { - hidden = !hidden - - stat(page = StatPage.RecoveryPhrase, event = StatEvent.ToggleHidden) + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + ) { + VSpacer(12.dp) + TextImportantWarning( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.PrivateKeys_NeverShareWarning) + ) + VSpacer(24.dp) + var hidden by remember { mutableStateOf(true) } + SeedPhraseList(viewModel.wordsNumbered, hidden) { + hidden = !hidden + stat(page = StatPage.RecoveryPhrase, event = StatEvent.ToggleHidden) + } + VSpacer(24.dp) + PassphraseCell(viewModel.passphrase, hidden) } - Spacer(Modifier.height(24.dp)) - PassphraseCell(viewModel.passphrase, hidden) - } - ActionButton(R.string.Alert_Copy) { - coroutineScope.launch { - sheetState.show() + ActionButton(R.string.Alert_Copy) { + coroutineScope.launch { + sheetState.show() + } } } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/showextendedkey/ShowExtendedKeyFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/showextendedkey/ShowExtendedKeyFragment.kt index 230276f805d..e5d8fdfd73c 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/showextendedkey/ShowExtendedKeyFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/manageaccount/showextendedkey/ShowExtendedKeyFragment.kt @@ -1,13 +1,11 @@ package io.horizontalsystems.bankwallet.modules.manageaccount.showextendedkey import android.os.Parcelable -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState @@ -16,6 +14,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -56,6 +55,7 @@ import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal import io.horizontalsystems.bankwallet.ui.compose.components.SelectorDialogCompose import io.horizontalsystems.bankwallet.ui.compose.components.SelectorItem import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantWarning +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.body_leah import io.horizontalsystems.bankwallet.ui.compose.components.subhead1_grey import io.horizontalsystems.bankwallet.ui.helpers.TextHelper @@ -133,144 +133,154 @@ private fun ShowExtendedKeyScreen( ) } ) { - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = viewModel.title.getString(), - navigationIcon = { - HsBackButton(onClick = navController::popBackStack) - }, - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Info_Title), - icon = R.drawable.ic_info_24, - onClick = { - FaqManager.showFaqPage(navController, FaqManager.faqPathPrivateKeys) - - viewModel.logEvent(StatEvent.Open(StatPage.Info)) - } + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = viewModel.title.getString(), + navigationIcon = { + HsBackButton(onClick = navController::popBackStack) + }, + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Info_Title), + icon = R.drawable.ic_info_24, + onClick = { + FaqManager.showFaqPage(navController, FaqManager.faqPathPrivateKeys) + viewModel.logEvent(StatEvent.Open(StatPage.Info)) + } + ) ) ) - ) - + } + ) { paddingValues -> Column( - modifier = Modifier - .weight(1f) - .verticalScroll(rememberScrollState()), - verticalArrangement = Arrangement.Top + modifier = Modifier.padding(paddingValues), ) { - Spacer(Modifier.height(12.dp)) + Column( + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Top + ) { + VSpacer(12.dp) - if (viewModel.displayKeyType.isPrivate) { - TextImportantWarning( - modifier = Modifier.padding(horizontal = 16.dp), - text = stringResource(R.string.PrivateKeys_NeverShareWarning) - ) - Spacer(Modifier.height(24.dp)) - } - - var showBlockchainSelectorDialog by remember { mutableStateOf(false) } - var showPurposeSelectorDialog by remember { mutableStateOf(false) } - var showAccountSelectorDialog by remember { mutableStateOf(false) } - - val menuItems = buildList<@Composable () -> Unit> { - add { - MenuItem( - title = stringResource(R.string.ExtendedKey_Purpose), - value = viewModel.purpose.name, - onClick = if (viewModel.displayKeyType == DisplayKeyType.Bip32RootKey || viewModel.displayKeyType.isDerivable) { - { showPurposeSelectorDialog = true } - } else { - null - } + if (viewModel.displayKeyType.isPrivate) { + TextImportantWarning( + modifier = Modifier.padding(horizontal = 16.dp), + text = stringResource(R.string.PrivateKeys_NeverShareWarning) ) + VSpacer(24.dp) } - if (viewModel.displayKeyType.isDerivable) { + + var showBlockchainSelectorDialog by remember { mutableStateOf(false) } + var showPurposeSelectorDialog by remember { mutableStateOf(false) } + var showAccountSelectorDialog by remember { mutableStateOf(false) } + + val menuItems = buildList<@Composable () -> Unit> { add { MenuItem( - title = stringResource(R.string.ExtendedKey_Blockchain), - value = viewModel.blockchain.name, - onClick = { showBlockchainSelectorDialog = true } + title = stringResource(R.string.ExtendedKey_Purpose), + value = viewModel.purpose.name, + onClick = if (viewModel.displayKeyType == DisplayKeyType.Bip32RootKey || viewModel.displayKeyType.isDerivable) { + { showPurposeSelectorDialog = true } + } else { + null + } ) } - add { - MenuItem( - title = stringResource(R.string.ExtendedKey_Account), - value = viewModel.account.toString(), - infoButtonClick = { - navController.slideFromBottom(R.id.кeyAccountInfoFragment) - }, - onClick = { showAccountSelectorDialog = true } - ) + if (viewModel.displayKeyType.isDerivable) { + add { + MenuItem( + title = stringResource(R.string.ExtendedKey_Blockchain), + value = viewModel.blockchain.name, + onClick = { showBlockchainSelectorDialog = true } + ) + } + add { + MenuItem( + title = stringResource(R.string.ExtendedKey_Account), + value = viewModel.account.toString(), + infoButtonClick = { + navController.slideFromBottom(R.id.кeyAccountInfoFragment) + }, + onClick = { showAccountSelectorDialog = true } + ) + } } } - } - if (menuItems.isNotEmpty()) { - CellUniversalLawrenceSection(menuItems) - } - - Spacer(Modifier.height(32.dp)) - if (viewModel.displayKeyType.isPrivate) { - HidableContent(viewModel.extendedKey, stringResource(R.string.ExtendedKey_TapToShowPrivateKey)) { - viewModel.logEvent(StatEvent.ToggleHidden) + if (menuItems.isNotEmpty()) { + CellUniversalLawrenceSection(menuItems) } - } else { - HidableContent(viewModel.extendedKey) - } - if (showPurposeSelectorDialog) { - SelectorDialogCompose( - title = stringResource(R.string.ExtendedKey_Purpose), - items = viewModel.purposes.map { - SelectorItem(it.name, it == viewModel.purpose, it) - }, - onDismissRequest = { - showPurposeSelectorDialog = false - }, - onSelectItem = { - viewModel.set(it) + VSpacer(32.dp) + if (viewModel.displayKeyType.isPrivate) { + HidableContent( + viewModel.extendedKey, + stringResource(R.string.ExtendedKey_TapToShowPrivateKey) + ) { + viewModel.logEvent(StatEvent.ToggleHidden) } - ) - } - if (showBlockchainSelectorDialog) { - SelectorDialogCompose( - title = stringResource(R.string.ExtendedKey_Blockchain), - items = viewModel.blockchains.map { - SelectorItem(it.name, it == viewModel.blockchain, it) - }, - onDismissRequest = { - showBlockchainSelectorDialog = false - }, - onSelectItem = { - viewModel.set(it) - } - ) + } else { + HidableContent(viewModel.extendedKey) + } + + if (showPurposeSelectorDialog) { + SelectorDialogCompose( + title = stringResource(R.string.ExtendedKey_Purpose), + items = viewModel.purposes.map { + SelectorItem(it.name, it == viewModel.purpose, it) + }, + onDismissRequest = { + showPurposeSelectorDialog = false + }, + onSelectItem = { + viewModel.set(it) + } + ) + } + if (showBlockchainSelectorDialog) { + SelectorDialogCompose( + title = stringResource(R.string.ExtendedKey_Blockchain), + items = viewModel.blockchains.map { + SelectorItem(it.name, it == viewModel.blockchain, it) + }, + onDismissRequest = { + showBlockchainSelectorDialog = false + }, + onSelectItem = { + viewModel.set(it) + } + ) + } + if (showAccountSelectorDialog) { + SelectorDialogCompose( + title = stringResource(R.string.ExtendedKey_Account), + items = viewModel.accounts.map { + SelectorItem(it.toString(), it == viewModel.account, it) + }, + onDismissRequest = { + showAccountSelectorDialog = false + }, + onSelectItem = { + viewModel.set(it) + } + ) + } } - if (showAccountSelectorDialog) { - SelectorDialogCompose( - title = stringResource(R.string.ExtendedKey_Account), - items = viewModel.accounts.map { - SelectorItem(it.toString(), it == viewModel.account, it) - }, - onDismissRequest = { - showAccountSelectorDialog = false - }, - onSelectItem = { - viewModel.set(it) + + ActionButton(R.string.Alert_Copy) { + if (viewModel.displayKeyType.isPrivate) { + coroutineScope.launch { + sheetState.show() } - ) - } - } - ActionButton(R.string.Alert_Copy) { - if (viewModel.displayKeyType.isPrivate) { - coroutineScope.launch { - sheetState.show() - } - } else { - TextHelper.copyText(viewModel.extendedKey) - HudHelper.showSuccessMessage(view, R.string.Hud_Text_Copied) + } else { + TextHelper.copyText(viewModel.extendedKey) + HudHelper.showSuccessMessage(view, R.string.Hud_Text_Copied) - viewModel.logEvent(StatEvent.Copy(StatEntity.Key)) + viewModel.logEvent(StatEvent.Copy(StatEntity.Key)) + } } } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/managewallets/ManageWalletsService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/managewallets/ManageWalletsService.kt index d9ce88e9ce2..f8fbdb0ea60 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/managewallets/ManageWalletsService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/managewallets/ManageWalletsService.kt @@ -132,7 +132,8 @@ class ManageWalletsService( is TokenType.AddressTyped, is TokenType.Eip20, is TokenType.Bep2, - is TokenType.Spl -> true + is TokenType.Spl, + is TokenType.Jetton -> true else -> false } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/favorites/MarketFavoritesService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/favorites/MarketFavoritesService.kt index 0b51b4a4c7d..8c877d250e3 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/favorites/MarketFavoritesService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/favorites/MarketFavoritesService.kt @@ -10,6 +10,7 @@ import io.horizontalsystems.bankwallet.modules.market.category.MarketItemWrapper import io.horizontalsystems.bankwallet.modules.market.filters.TimePeriod import io.horizontalsystems.bankwallet.modules.market.sort import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.marketkit.models.Analytics import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject @@ -38,7 +39,7 @@ class MarketFavoritesService( private val currencyManager: CurrencyManager, private val backgroundManager: BackgroundManager, private val priceManager: PriceManager -) : BackgroundManager.Listener { +) { private val coroutineScope = CoroutineScope(Dispatchers.Default) private var favoritesJob: Job? = null private var marketItems: List = listOf() @@ -121,10 +122,6 @@ class MarketFavoritesService( } } - override fun willEnterForeground() { - fetch() - } - fun removeFavorite(uid: String) { repository.removeFavorite(uid) } @@ -137,7 +134,13 @@ class MarketFavoritesService( } fun start() { - backgroundManager.registerListener(this) + coroutineScope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterForeground) { + fetch() + } + } + } coroutineScope.launch { currencyManager.baseCurrencyUpdatedSignal.asFlow().collect { @@ -167,7 +170,6 @@ class MarketFavoritesService( } fun stop() { - backgroundManager.unregisterListener(this) coroutineScope.cancel() } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/filters/MarketFiltersFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/filters/MarketFiltersFragment.kt index 1d1011b0f3d..a1b1f566c39 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/filters/MarketFiltersFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/filters/MarketFiltersFragment.kt @@ -17,7 +17,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.Surface +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -107,8 +107,9 @@ private fun AdvancedSearchScreen( ) }, ) { - Surface(color = ComposeAppTheme.colors.tyler) { - Column { + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { AppBar( title = stringResource(R.string.Market_Filters), navigationIcon = { @@ -121,7 +122,11 @@ private fun AdvancedSearchScreen( ) ), ) - + } + ) { paddingValues -> + Column( + modifier = Modifier.padding(paddingValues) + ) { Column( modifier = Modifier .weight(1f) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/filters/MarketFiltersService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/filters/MarketFiltersService.kt index 5b1102a5d59..9af5f390169 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/filters/MarketFiltersService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/filters/MarketFiltersService.kt @@ -32,6 +32,7 @@ class MarketFiltersService( BlockchainType.Unsupported("moonriver"), BlockchainType.Unsupported("okex-chain"), BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Polygon, BlockchainType.Unsupported("solana"), BlockchainType.Unsupported("sora"), diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewService.kt index dd6a57ab04a..96d5dc2dced 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/overview/MarketOverviewService.kt @@ -6,6 +6,7 @@ import io.horizontalsystems.bankwallet.modules.market.TimeDuration import io.horizontalsystems.bankwallet.modules.market.TopMarket import io.horizontalsystems.bankwallet.modules.market.topcoins.MarketTopMoversRepository import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.marketkit.models.MarketOverview import io.horizontalsystems.marketkit.models.TopMovers import io.reactivex.subjects.BehaviorSubject @@ -22,7 +23,7 @@ class MarketOverviewService( private val marketKit: MarketKitWrapper, private val backgroundManager: BackgroundManager, private val currencyManager: CurrencyManager -) : BackgroundManager.Listener { +) { private val coroutineScope = CoroutineScope(Dispatchers.Default) private var topMoversJob: Job? = null private var marketOverviewJob: Job? = null @@ -67,7 +68,13 @@ class MarketOverviewService( } fun start() { - backgroundManager.registerListener(this) + coroutineScope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterForeground) { + forceRefresh() + } + } + } coroutineScope.launch { currencyManager.baseCurrencyUpdatedSignal.asFlow().collect { @@ -79,14 +86,9 @@ class MarketOverviewService( } fun stop() { - backgroundManager.unregisterListener(this) coroutineScope.cancel() } - override fun willEnterForeground() { - forceRefresh() - } - fun refresh() { forceRefresh() } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/posts/MarketPostService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/posts/MarketPostService.kt index 83ad41ddd2f..3de4c03f657 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/posts/MarketPostService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/market/posts/MarketPostService.kt @@ -3,6 +3,7 @@ package io.horizontalsystems.bankwallet.modules.market.posts import io.horizontalsystems.bankwallet.core.managers.MarketKitWrapper import io.horizontalsystems.bankwallet.entities.DataState import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.marketkit.models.Post import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject @@ -16,7 +17,7 @@ import kotlinx.coroutines.rx2.await class MarketPostService( private val marketKit: MarketKitWrapper, private val backgroundManager: BackgroundManager, -) : BackgroundManager.Listener { +) { private val coroutineScope = CoroutineScope(Dispatchers.Default) private var job: Job? = null @@ -24,10 +25,6 @@ class MarketPostService( val stateObservable: Observable>> get() = stateSubject - init { - backgroundManager.registerListener(this) - } - private fun fetchPosts() { job?.cancel() job = coroutineScope.launch { @@ -40,17 +37,19 @@ class MarketPostService( } } - override fun willEnterForeground() { - fetchPosts() - } - fun start() { fetchPosts() + coroutineScope.launch { + backgroundManager.stateFlow.collect { state -> + if (state == BackgroundManagerState.EnterForeground) { + fetchPosts() + } + } + } } fun stop() { coroutineScope.cancel() - backgroundManager.unregisterListener(this) } fun refresh() { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/BaseUniswapV3Provider.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/BaseUniswapV3Provider.kt index 83973219c0b..e3026ade4c2 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/BaseUniswapV3Provider.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/BaseUniswapV3Provider.kt @@ -154,6 +154,7 @@ abstract class BaseUniswapV3Provider(dexType: DexType) : EvmSwapProvider() { BlockchainType.BinanceSmartChain, BlockchainType.Polygon, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne -> uniswapV3Kit.etherToken(chain) else -> throw Exception("Invalid coin for swap: $token") } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/OneInchProvider.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/OneInchProvider.kt index b57007de91e..f036ce1bc7c 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/OneInchProvider.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/OneInchProvider.kt @@ -46,6 +46,7 @@ object OneInchProvider : EvmSwapProvider() { BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/UniswapV3Provider.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/UniswapV3Provider.kt index 1da5b507fd1..2d5b90812b5 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/UniswapV3Provider.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/providers/UniswapV3Provider.kt @@ -16,7 +16,8 @@ object UniswapV3Provider : BaseUniswapV3Provider(DexType.Uniswap) { BlockchainType.ArbitrumOne, // BlockchainType.Optimism, BlockchainType.Polygon, - BlockchainType.BinanceSmartChain + BlockchainType.BinanceSmartChain, + BlockchainType.Base, -> true else -> false } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/sendtransaction/SendTransactionServiceFactory.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/sendtransaction/SendTransactionServiceFactory.kt index f886dc35a84..40b4ffcfc26 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/sendtransaction/SendTransactionServiceFactory.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/multiswap/sendtransaction/SendTransactionServiceFactory.kt @@ -10,6 +10,7 @@ object SendTransactionServiceFactory { BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne, BlockchainType.Gnosis, BlockchainType.Fantom -> SendTransactionServiceEvm(blockchainType) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/nft/send/SendNftFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/nft/send/SendNftFragment.kt index f98acae8cc5..8cce525ddc3 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/nft/send/SendNftFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/nft/send/SendNftFragment.kt @@ -89,18 +89,13 @@ private fun getFactory(nftUidString: String): SendNftModule.Factory? { val evmNftRecord = (nftRecord as? EvmNftRecord) ?: return null - val evmKitWrapper = App.evmBlockchainManager - .getEvmKitManager(nftUid.blockchainType) - .getEvmKitWrapper(account, nftUid.blockchainType) - return SendNftModule.Factory( evmNftRecord, nftUid, nftRecord.balance, adapter, SendEvmAddressService(), - App.nftMetadataManager, - evmKitWrapper + App.nftMetadataManager ) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/nft/send/SendNftModule.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/nft/send/SendNftModule.kt index a5152b2ba77..5af9e7bcf66 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/nft/send/SendNftModule.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/nft/send/SendNftModule.kt @@ -3,7 +3,6 @@ package io.horizontalsystems.bankwallet.modules.nft.send import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.horizontalsystems.bankwallet.core.adapters.nft.INftAdapter -import io.horizontalsystems.bankwallet.core.managers.EvmKitWrapper import io.horizontalsystems.bankwallet.core.managers.NftMetadataManager import io.horizontalsystems.bankwallet.core.utils.AddressUriParser import io.horizontalsystems.bankwallet.entities.DataState @@ -21,8 +20,7 @@ object SendNftModule { val nftBalance: Int, private val adapter: INftAdapter, private val sendEvmAddressService: SendEvmAddressService, - private val nftMetadataManager: NftMetadataManager, - private val evmKitWrapper: EvmKitWrapper + private val nftMetadataManager: NftMetadataManager ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/pin/PinComponent.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/pin/PinComponent.kt index 0795b8085b1..abbc22ed412 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/pin/PinComponent.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/pin/PinComponent.kt @@ -1,22 +1,47 @@ package io.horizontalsystems.bankwallet.modules.pin -import android.app.Activity import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.bankwallet.core.managers.UserManager import io.horizontalsystems.bankwallet.modules.pin.core.LockManager import io.horizontalsystems.bankwallet.modules.pin.core.PinDbStorage import io.horizontalsystems.bankwallet.modules.pin.core.PinManager +import io.horizontalsystems.core.BackgroundManager +import io.horizontalsystems.core.BackgroundManagerState import io.horizontalsystems.core.IPinComponent import io.horizontalsystems.core.IPinSettingsStorage import io.reactivex.BackpressureStrategy import io.reactivex.Flowable +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch class PinComponent( private val pinSettingsStorage: IPinSettingsStorage, private val userManager: UserManager, - private val pinDbStorage: PinDbStorage + private val pinDbStorage: PinDbStorage, + private val backgroundManager: BackgroundManager ) : IPinComponent { + private val scope = CoroutineScope(Dispatchers.Default) + + init { + scope.launch { + backgroundManager.stateFlow.collect { state -> + when (state) { + BackgroundManagerState.EnterForeground -> { + willEnterForeground() + } + BackgroundManagerState.EnterBackground -> { + didEnterBackground() + } + BackgroundManagerState.AllActivitiesDestroyed -> { + lock() + } + } + } + } + } + private val pinManager: PinManager by lazy { PinManager(pinDbStorage) } @@ -106,7 +131,7 @@ class PinComponent( appLockManager.updateLastExitDate() } - override fun willEnterForeground(activity: Activity) { + override fun willEnterForeground() { appLockManager.willEnterForeground() } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/restoreaccount/restoreblockchains/RestoreBlockchains.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/restoreaccount/restoreblockchains/RestoreBlockchains.kt index 83aa1e1f29d..5e845120d4a 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/restoreaccount/restoreblockchains/RestoreBlockchains.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/restoreaccount/restoreblockchains/RestoreBlockchains.kt @@ -2,7 +2,6 @@ package io.horizontalsystems.bankwallet.modules.restoreaccount.restoreblockchain import android.widget.Toast import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -10,7 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.Divider @@ -18,6 +16,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -45,6 +44,7 @@ import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme import io.horizontalsystems.bankwallet.ui.compose.TranslatableString import io.horizontalsystems.bankwallet.ui.compose.components.AppBar import io.horizontalsystems.bankwallet.ui.compose.components.CellMultilineClear +import io.horizontalsystems.bankwallet.ui.compose.components.HSpacer import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.HsIconButton import io.horizontalsystems.bankwallet.ui.compose.components.HsSwitch @@ -153,24 +153,29 @@ fun ManageWalletsScreen( } }, ) { - Column( - modifier = Modifier.background(color = ComposeAppTheme.colors.tyler) - ) { - AppBar( - title = stringResource(R.string.Restore_Title), - navigationIcon = { - HsBackButton(onClick = onBackClick) - }, - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Button_Restore), - onClick = { viewModel.onRestore() }, - enabled = doneButtonEnabled - ) - ), - ) - - LazyColumn { + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.Restore_Title), + navigationIcon = { + HsBackButton(onClick = onBackClick) + }, + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Button_Restore), + onClick = { viewModel.onRestore() }, + enabled = doneButtonEnabled + ) + ), + ) + } + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { item { Spacer(modifier = Modifier.height(12.dp)) Divider( @@ -208,7 +213,7 @@ fun ManageWalletsScreen( modifier = Modifier.padding(top = 1.dp) ) } - Spacer(Modifier.width(12.dp)) + HSpacer(12.dp) if (viewItem.hasSettings) { HsIconButton( onClick = { viewModel.onClickSettings(viewItem.item) } @@ -233,7 +238,10 @@ fun ManageWalletsScreen( } } -private fun onItemClick(viewItem: CoinViewItem, viewModel: RestoreBlockchainsViewModel) { +private fun onItemClick( + viewItem: CoinViewItem, + viewModel: RestoreBlockchainsViewModel +) { if (viewItem.enabled) { viewModel.disable(viewItem.item) } else { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/SendFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/SendFragment.kt index 1649409a2b7..70674b3e07f 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/SendFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/SendFragment.kt @@ -123,6 +123,7 @@ class SendFragment : BaseFragment() { BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.Gnosis, BlockchainType.Fantom, BlockchainType.ArbitrumOne -> { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/SendBitcoinScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/SendBitcoinScreen.kt index 1cdddf6e74b..06c076ff8e5 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/SendBitcoinScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/SendBitcoinScreen.kt @@ -100,10 +100,6 @@ fun SendBitcoinNavHost( UtxoExpertModeScreen( adapter = viewModel.adapter, token = viewModel.wallet.token, - address = viewModel.uiState.address, - memo = viewModel.uiState.memo, - value = viewModel.uiState.amount, - feeRate = viewModel.uiState.feeRate, customUnspentOutputs = viewModel.customUnspentOutputs, updateUnspentOutputs = { viewModel.updateCustomUnspentOutputs(it) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/advanced/SendBtcAdvancedSettingsScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/advanced/SendBtcAdvancedSettingsScreen.kt index c954a587854..5b19856cfb6 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/advanced/SendBtcAdvancedSettingsScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/advanced/SendBtcAdvancedSettingsScreen.kt @@ -1,6 +1,5 @@ package io.horizontalsystems.bankwallet.modules.send.bitcoin.advanced -import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box @@ -16,6 +15,7 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -51,6 +51,7 @@ import io.horizontalsystems.bankwallet.ui.compose.components.MenuItem import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantError import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantWarning +import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.body_leah import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_grey import io.horizontalsystems.bankwallet.ui.extensions.BottomSheetHeader @@ -101,25 +102,33 @@ fun SendBtcAdvancedSettingsScreen( ) }, ) { - Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { - AppBar( - title = stringResource(R.string.Send_Advanced), - navigationIcon = { - HsBackButton(onClick = { navController.popBackStack() }) - }, - menuItems = listOf( - MenuItem( - title = TranslatableString.ResString(R.string.Button_Reset), - onClick = { - sendBitcoinViewModel.reset() - viewModel.reset() - } + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { + AppBar( + title = stringResource(R.string.Send_Advanced), + navigationIcon = { + HsBackButton(onClick = { navController.popBackStack() }) + }, + menuItems = listOf( + MenuItem( + title = TranslatableString.ResString(R.string.Button_Reset), + onClick = { + sendBitcoinViewModel.reset() + viewModel.reset() + } + ) ) ) - ) - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + ) { - Spacer(modifier = Modifier.height(12.dp)) + VSpacer(12.dp) CellUniversalLawrenceSection( listOf { HSFeeRaw( @@ -133,8 +142,8 @@ fun SendBtcAdvancedSettingsScreen( } ) - if(feeRateVisible) { - Spacer(modifier = Modifier.height(24.dp)) + if (feeRateVisible) { + VSpacer(24.dp) EvmSettingsInput( title = stringResource(R.string.FeeSettings_FeeRate), info = stringResource(R.string.FeeSettings_FeeRate_Info), @@ -157,7 +166,7 @@ fun SendBtcAdvancedSettingsScreen( ) } - Spacer(modifier = Modifier.height(24.dp)) + VSpacer(24.dp) TransactionDataSortSettings( navController, viewModel.uiState.transactionSortTitle, @@ -168,7 +177,7 @@ fun SendBtcAdvancedSettingsScreen( } if (lockTimeEnabled) { - Spacer(Modifier.height(32.dp)) + VSpacer(32.dp) CellUniversalLawrenceSection( listOf { HSHodlerInput( @@ -185,7 +194,7 @@ fun SendBtcAdvancedSettingsScreen( ) } - Spacer(Modifier.height(32.dp)) + VSpacer(32.dp) CellUniversalLawrenceSection( listOf { UtxoSwitch( @@ -198,7 +207,7 @@ fun SendBtcAdvancedSettingsScreen( text = stringResource(R.string.Send_Utxo_Description), ) - Spacer(Modifier.height(32.dp)) + VSpacer(32.dp) CellUniversalLawrenceSection { RbfSwitch( enabled = viewModel.uiState.rbfEnabled, @@ -212,12 +221,16 @@ fun SendBtcAdvancedSettingsScreen( feeRateCaution?.let { FeeRateCaution( - modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 12.dp), + modifier = Modifier.padding( + start = 16.dp, + end = 16.dp, + top = 12.dp + ), feeRateCaution = it ) } - Spacer(Modifier.height(32.dp)) + VSpacer(32.dp) } } } @@ -362,6 +375,7 @@ fun FeeRateCaution(modifier: Modifier, feeRateCaution: HSCaution) { text = feeRateCaution.getDescription() ?: "" ) } + HSCaution.Type.Warning -> { TextImportantWarning( modifier = modifier, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeScreen.kt index eb9ff471125..db27ba133ba 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeScreen.kt @@ -1,8 +1,10 @@ package io.horizontalsystems.bankwallet.modules.send.bitcoin.utxoexpert import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -10,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -21,33 +24,26 @@ import androidx.lifecycle.viewmodel.compose.viewModel import io.horizontalsystems.bankwallet.R import io.horizontalsystems.bankwallet.core.ISendBitcoinAdapter import io.horizontalsystems.bankwallet.core.shorten -import io.horizontalsystems.bankwallet.entities.Address -import io.horizontalsystems.bankwallet.modules.evmfee.ButtonsGroupWithShade import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme import io.horizontalsystems.bankwallet.ui.compose.components.AppBar -import io.horizontalsystems.bankwallet.ui.compose.components.ButtonPrimaryYellow +import io.horizontalsystems.bankwallet.ui.compose.components.ButtonSecondaryTransparent +import io.horizontalsystems.bankwallet.ui.compose.components.CellUniversalLawrenceSection import io.horizontalsystems.bankwallet.ui.compose.components.HSpacer import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.HsCheckbox import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal import io.horizontalsystems.bankwallet.ui.compose.components.SectionItemBorderedRowUniversalClear -import io.horizontalsystems.bankwallet.ui.compose.components.SectionUniversalItem import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_grey import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_leah import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_lucian import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo import io.horizontalsystems.marketkit.models.Token -import java.math.BigDecimal @Composable fun UtxoExpertModeScreen( adapter: ISendBitcoinAdapter, token: Token, - address: Address?, - memo: String?, - value: BigDecimal?, - feeRate: Int?, customUnspentOutputs: List?, updateUnspentOutputs: (List) -> Unit, onBackClick: () -> Unit @@ -57,10 +53,6 @@ fun UtxoExpertModeScreen( factory = UtxoExpertModeModule.Factory( adapter, token, - address, - memo, - value, - feeRate, customUnspentOutputs ) ) @@ -83,13 +75,11 @@ fun UtxoExpertModeScreen( .padding(it) .fillMaxSize() ) { - Column( - modifier = Modifier.background(ComposeAppTheme.colors.lawrence) - ) { - UtxoInfoSection( - uiState.sendToInfo, - uiState.changeInfo, - uiState.feeInfo, + CellUniversalLawrenceSection { + UtxoInfoCell( + title = stringResource(R.string.Send_Utxo_AvailableBalance), + value = uiState.availableBalanceInfo.value, + subValue = uiState.availableBalanceInfo.subValue ) } Box( @@ -103,14 +93,39 @@ fun UtxoExpertModeScreen( } ) } - ButtonsGroupWithShade { - ButtonPrimaryYellow( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp), - title = stringResource(R.string.Button_Done), - onClick = onBackClick, + Box( + modifier = Modifier + .height(62.dp) + .fillMaxWidth() + ){ + Divider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = ComposeAppTheme.colors.steel10 ) + Row( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + ButtonSecondaryTransparent( + title = stringResource(id = R.string.Send_Utxo_UnselectAll), + enabled = uiState.unselectAllIsEnabled, + onClick = { + viewModel.unselectAll() + updateUnspentOutputs(viewModel.customOutputs) + } + ) + ButtonSecondaryTransparent( + title = stringResource(id = R.string.Send_Utxo_SelectAll), + onClick = { + viewModel.selectAll() + updateUnspentOutputs(viewModel.customOutputs) + } + ) + } } } } @@ -150,53 +165,9 @@ private fun UtxoList( } } -@Composable -private fun UtxoInfoSection( - sendToInfo: UtxoExpertModeModule.InfoItem, - changeInfo: UtxoExpertModeModule.InfoItem?, - feeInfo: UtxoExpertModeModule.InfoItem -) { - val infoItems = buildList<@Composable () -> Unit> { - add { - UtxoInfoCell( - title = stringResource(R.string.Send_Utxo_SendTo), - subtitle = sendToInfo.subTitle, - value = sendToInfo.value, - subValue = sendToInfo.subValue - ) - } - changeInfo?.let { - add { - UtxoInfoCell( - title = stringResource(R.string.Send_Utxo_Change), - subtitle = changeInfo.subTitle, - value = changeInfo.value, - subValue = changeInfo.subValue - ) - } - } - add { - UtxoInfoCell( - title = stringResource(R.string.Send_Fee), - subtitle = feeInfo.subTitle, - value = feeInfo.value, - subValue = feeInfo.subValue - ) - } - } - infoItems.forEachIndexed { index, composable -> - SectionUniversalItem( - borderTop = index != 0, - ) { - composable() - } - } -} - @Composable private fun UtxoInfoCell( title: String, - subtitle: String?, value: String?, subValue: String? ) { @@ -211,9 +182,6 @@ private fun UtxoInfoCell( modifier = Modifier.weight(1f) ) { subhead2_leah(text = title) - subtitle?.let { - subhead2_grey(text = it) - } } Column( horizontalAlignment = Alignment.End diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeViewModel.kt index c05b3008421..6fbe9d478f0 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeViewModel.kt @@ -7,9 +7,7 @@ import cash.z.ecc.android.sdk.ext.collectWith import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.bankwallet.core.ISendBitcoinAdapter import io.horizontalsystems.bankwallet.core.ViewModelUiState -import io.horizontalsystems.bankwallet.core.shorten import io.horizontalsystems.bankwallet.core.toHexString -import io.horizontalsystems.bankwallet.entities.Address import io.horizontalsystems.bankwallet.modules.xrate.XRateService import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo import io.horizontalsystems.core.helpers.DateHelper @@ -20,30 +18,16 @@ import java.util.Date class UtxoExpertModeViewModel( private val adapter: ISendBitcoinAdapter, private val token: Token, - private val address: Address?, - private val memo: String?, - private val feeRate: Int?, - initialValue: BigDecimal?, initialCustomUnspentOutputs: List?, xRateService: XRateService, ) : ViewModelUiState() { - private var value = initialValue ?: BigDecimal.ZERO private var unspentOutputViewItems = listOf() private var selectedUnspentOutputs = listOf() private var coinRate = xRateService.getRate(token.coin.uid) - private var sendToInfo = UtxoExpertModeModule.InfoItem( - subTitle = address?.hex?.shorten(), - value = App.numberFormatter.formatCoinFull(value, token.coin.code, token.decimals), - subValue = coinRate?.let { rate -> - rate.copy(value = value.times(rate.value)).getFormattedFull() - } ?: "", - ) - private var changeInfo: UtxoExpertModeModule.InfoItem? = null - private var feeInfo = UtxoExpertModeModule.InfoItem( - subTitle = null, - value = null, - subValue = null, + private var availableBalanceInfo = UtxoExpertModeModule.InfoItem( + value = App.numberFormatter.formatCoinFull(BigDecimal.ZERO, token.coin.code, token.decimals), + subValue = "", ) val customOutputs: List @@ -57,73 +41,33 @@ class UtxoExpertModeViewModel( coinRate = it setUnspentOutputViewItems() } + setAvailableBalanceInfo() setUnspentOutputViewItems() - if (selectedUnspentOutputs.isNotEmpty()) { - updateFee() - } emitState() } override fun createState() = UtxoExpertModeModule.UiState( - sendToInfo = sendToInfo, - changeInfo = changeInfo, - feeInfo = feeInfo, + availableBalanceInfo = availableBalanceInfo, utxoItems = unspentOutputViewItems, + unselectAllIsEnabled = selectedUnspentOutputs.isNotEmpty(), ) private fun getUnspentId(unspentOutputInfo: UnspentOutputInfo) = "${unspentOutputInfo.transactionHash.toHexString()}-${unspentOutputInfo.outputIndex}" - fun onUnspentOutputClicked(id: String) { - selectedUnspentOutputs = if (selectedUnspentOutputs.contains(id)) { - selectedUnspentOutputs.filter { it != id } - } else { - selectedUnspentOutputs + id + private fun setAvailableBalanceInfo() { + var totalCoinValue = BigDecimal.ZERO + adapter.unspentOutputs.map { utxo -> + val utxoId = getUnspentId(utxo) + if (selectedUnspentOutputs.isEmpty() || selectedUnspentOutputs.contains(utxoId)) { + totalCoinValue += utxo.value.toBigDecimal().movePointLeft(token.decimals) + } } - unspentOutputViewItems = unspentOutputViewItems.map { utxo -> - utxo.copy(selected = selectedUnspentOutputs.contains(utxo.id)) - } - updateFee() - emitState() - } - - private fun updateFee() { - val feeRateNonNull = feeRate ?: return - val bitcoinFeeInfo = adapter.bitcoinFeeInfo( - amount = value, - feeRate = feeRateNonNull, - address = address?.hex, - memo = memo, - unspentOutputs = customOutputs, - pluginData = null, - ) - val fee = bitcoinFeeInfo?.fee - if (fee == null) { - feeInfo = feeInfo.copy( - value = null, - subValue = null, - ) - changeInfo = null - return - } - feeInfo = feeInfo.copy( - value = App.numberFormatter.formatCoinFull(fee, token.coin.code, token.decimals), + availableBalanceInfo = availableBalanceInfo.copy( + value = App.numberFormatter.formatCoinFull(totalCoinValue, token.coin.code, token.decimals), subValue = coinRate?.let { rate -> - rate.copy(value = fee.times(rate.value)).getFormattedFull() + rate.copy(value = totalCoinValue.times(rate.value)).getFormattedFull() } ?: "", ) - changeInfo = if (bitcoinFeeInfo.changeAddress != null && bitcoinFeeInfo.changeValue != null) { - val value = bitcoinFeeInfo.changeValue - UtxoExpertModeModule.InfoItem( - subTitle = bitcoinFeeInfo.changeAddress.stringValue.shorten(), - value = App.numberFormatter.formatCoinFull(value, token.coin.code, token.decimals), - subValue = coinRate?.let { rate -> - rate.copy(value = value.times(rate.value)).getFormattedFull() - } ?: "", - ) - } else { - null - } - emitState() } private fun setUnspentOutputViewItems() { @@ -144,6 +88,37 @@ class UtxoExpertModeViewModel( } } + private fun updateUtxoSelectedState() { + unspentOutputViewItems = unspentOutputViewItems.map { utxo -> + utxo.copy(selected = selectedUnspentOutputs.contains(utxo.id)) + } + } + + fun onUnspentOutputClicked(id: String) { + selectedUnspentOutputs = if (selectedUnspentOutputs.contains(id)) { + selectedUnspentOutputs.filter { it != id } + } else { + selectedUnspentOutputs + id + } + updateUtxoSelectedState() + setAvailableBalanceInfo() + emitState() + } + + fun unselectAll() { + selectedUnspentOutputs = listOf() + updateUtxoSelectedState() + setAvailableBalanceInfo() + emitState() + } + + fun selectAll() { + selectedUnspentOutputs = unspentOutputViewItems.map { it.id } + updateUtxoSelectedState() + setAvailableBalanceInfo() + emitState() + } + } object UtxoExpertModeModule { @@ -152,20 +127,12 @@ object UtxoExpertModeModule { class Factory( private val adapter: ISendBitcoinAdapter, private val token: Token, - private val address: Address?, - private val memo: String?, - private val value: BigDecimal?, - private val feeRate: Int?, private val customUnspentOutputs: List?, ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return UtxoExpertModeViewModel( adapter = adapter, token = token, - address = address, - memo = memo, - feeRate = feeRate, - initialValue = value, initialCustomUnspentOutputs = customUnspentOutputs, xRateService = XRateService(App.marketKit, App.currencyManager.baseCurrency) ) as T @@ -173,14 +140,12 @@ object UtxoExpertModeModule { } data class UiState( - val sendToInfo: InfoItem, - val changeInfo: InfoItem?, - val feeInfo: InfoItem, + val availableBalanceInfo: InfoItem, val utxoItems: List, + val unselectAllIsEnabled: Boolean, ) data class InfoItem( - val subTitle: String?, val value: String?, val subValue: String?, ) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonAddressService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonAddressService.kt index 33e50916e12..7b2000e5b6b 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonAddressService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonAddressService.kt @@ -1,6 +1,9 @@ package io.horizontalsystems.bankwallet.modules.send.ton +import io.horizontalsystems.bankwallet.R +import io.horizontalsystems.bankwallet.core.providers.Translator import io.horizontalsystems.bankwallet.entities.Address +import io.horizontalsystems.tonkit.FriendlyAddress import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -8,8 +11,7 @@ import kotlinx.coroutines.flow.update class SendTonAddressService(prefilledAddress: String?) { private var address: Address? = prefilledAddress?.let { Address(it) } private var addressError: Throwable? = null - var tonAddress: String? = prefilledAddress - private set + private var tonAddress: FriendlyAddress? = prefilledAddress?.let { FriendlyAddress.parse(it) } private val _stateFlow = MutableStateFlow( State( @@ -34,7 +36,11 @@ class SendTonAddressService(prefilledAddress: String?) { tonAddress = null val address = this.address ?: return - tonAddress = address.hex + try { + tonAddress = FriendlyAddress.parse(address.hex) + } catch (e: Exception) { + addressError = Throwable(Translator.getString(R.string.SwapSettings_Error_InvalidAddress)) + } } private fun emitState() { @@ -50,7 +56,7 @@ class SendTonAddressService(prefilledAddress: String?) { data class State( val address: Address?, - val tonAddress: String?, + val tonAddress: FriendlyAddress?, val addressError: Throwable?, val canBeSend: Boolean ) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonAmountService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonAmountService.kt index 2f9076bf933..9e8aba15770 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonAmountService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonAmountService.kt @@ -10,12 +10,11 @@ import java.math.BigDecimal class SendTonAmountService( private val amountValidator: AmountValidator, private val coinCode: String, - private val maxBalance: BigDecimal, + private val availableBalance: BigDecimal, private val leaveSomeBalanceForFee: Boolean = false ) { private var amount: BigDecimal? = null private var amountCaution: HSCaution? = null - private var availableBalance: BigDecimal? = null private val _stateFlow = MutableStateFlow( State( @@ -49,7 +48,7 @@ class SendTonAmountService( amountCaution = amountValidator.validate( coinAmount = amount, coinCode = coinCode, - availableBalance = availableBalance ?: BigDecimal.ZERO, + availableBalance = availableBalance, leaveSomeBalanceForFee = leaveSomeBalanceForFee ) } @@ -62,14 +61,6 @@ class SendTonAmountService( emitState() } - fun setFee(fee: BigDecimal?) { - availableBalance = fee?.let { maxBalance - it } - - validateAmount() - - emitState() - } - data class State( val amount: BigDecimal?, val amountCaution: HSCaution?, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonFeeService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonFeeService.kt index a2d2947d5c5..af669235d32 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonFeeService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonFeeService.kt @@ -1,14 +1,16 @@ package io.horizontalsystems.bankwallet.modules.send.ton import io.horizontalsystems.bankwallet.core.ISendTonAdapter -import kotlinx.coroutines.Dispatchers +import io.horizontalsystems.tonkit.FriendlyAddress import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update -import kotlinx.coroutines.withContext import java.math.BigDecimal class SendTonFeeService(private val adapter: ISendTonAdapter) { + private var memo: String? = null + private var address: FriendlyAddress? = null + private var amount: BigDecimal? = null private var fee: BigDecimal? = null private val _stateFlow = MutableStateFlow( @@ -18,9 +20,36 @@ class SendTonFeeService(private val adapter: ISendTonAdapter) { ) val stateFlow = _stateFlow.asStateFlow() - suspend fun start() = withContext(Dispatchers.IO) { - fee = adapter.estimateFee() + private suspend fun refreshFee() { + val amount = amount + val address = address + val memo = memo + fee = if (amount != null && address != null) { + adapter.estimateFee(amount, address, memo) + } else { + null + } + } + + suspend fun setAmount(amount: BigDecimal?) { + this.amount = amount + + refreshFee() + emitState() + } + + suspend fun setTonAddress(address: FriendlyAddress?) { + this.address = address + + refreshFee() + emitState() + } + + suspend fun setMemo(memo: String?) { + this.memo = memo + + refreshFee() emitState() } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonModule.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonModule.kt index 81710a54215..e3768c425c8 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonModule.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonModule.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.bankwallet.core.ISendTonAdapter +import io.horizontalsystems.bankwallet.core.isNative import io.horizontalsystems.bankwallet.entities.Wallet import io.horizontalsystems.bankwallet.modules.amount.AmountValidator import io.horizontalsystems.bankwallet.modules.xrate.XRateService @@ -25,7 +26,12 @@ object SendTonModule { val amountValidator = AmountValidator() val coinMaxAllowedDecimals = wallet.token.decimals - val amountService = SendTonAmountService(amountValidator, wallet.coin.code, adapter.availableBalance) + val amountService = SendTonAmountService( + amountValidator = amountValidator, + coinCode = wallet.coin.code, + availableBalance = adapter.availableBalance, + leaveSomeBalanceForFee = wallet.token.type.isNative + ) val addressService = SendTonAddressService(predefinedAddress) val feeService = SendTonFeeService(adapter) val xRateService = XRateService(App.marketKit, App.currencyManager.baseCurrency) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonViewModel.kt index d7cb95bd81d..b4f5bf68fc0 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/ton/SendTonViewModel.kt @@ -4,7 +4,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.viewModelScope -import cash.z.ecc.android.sdk.ext.collectWith import io.horizontalsystems.bankwallet.R import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.bankwallet.core.AppLogger @@ -58,24 +57,30 @@ class SendTonViewModel( private val logger: AppLogger = AppLogger("send-ton") init { - amountService.stateFlow.collectWith(viewModelScope) { - handleUpdatedAmountState(it) + viewModelScope.launch(Dispatchers.Default) { + amountService.stateFlow.collect { + handleUpdatedAmountState(it) + } } - addressService.stateFlow.collectWith(viewModelScope) { - handleUpdatedAddressState(it) + viewModelScope.launch(Dispatchers.Default) { + addressService.stateFlow.collect { + handleUpdatedAddressState(it) + } } - feeService.stateFlow.collectWith(viewModelScope) { - handleUpdatedFeeState(it) + viewModelScope.launch(Dispatchers.Default) { + feeService.stateFlow.collect { + handleUpdatedFeeState(it) + } } - xRateService.getRateFlow(sendToken.coin.uid).collectWith(viewModelScope) { - coinRate = it + viewModelScope.launch(Dispatchers.Default) { + xRateService.getRateFlow(sendToken.coin.uid).collect { + coinRate = it + } } - xRateService.getRateFlow(feeToken.coin.uid).collectWith(viewModelScope) { - feeCoinRate = it - } - - viewModelScope.launch { - feeService.start() + viewModelScope.launch(Dispatchers.Default) { + xRateService.getRateFlow(feeToken.coin.uid).collect { + feeCoinRate = it + } } } @@ -122,7 +127,10 @@ class SendTonViewModel( } fun onEnterMemo(memo: String) { - this.memo = memo.ifBlank { null } + viewModelScope.launch(Dispatchers.Default) { + this@SendTonViewModel.memo = memo.ifBlank { null } + feeService.setMemo(this@SendTonViewModel.memo) + } } private suspend fun send() = withContext(Dispatchers.IO) { @@ -146,23 +154,25 @@ class SendTonViewModel( else -> HSCaution(TranslatableString.PlainString(error.message ?: "")) } - private fun handleUpdatedAmountState(amountState: SendTonAmountService.State) { + private suspend fun handleUpdatedAmountState(amountState: SendTonAmountService.State) { this.amountState = amountState + feeService.setAmount(amountState.amount) + emitState() } - private fun handleUpdatedAddressState(addressState: SendTonAddressService.State) { + private suspend fun handleUpdatedAddressState(addressState: SendTonAddressService.State) { this.addressState = addressState + feeService.setTonAddress(addressState.tonAddress) + emitState() } private fun handleUpdatedFeeState(feeState: SendTonFeeService.State) { this.feeState = feeState - amountService.setFee(feeState.fee) - emitState() } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppIconService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppIconService.kt index 433b9cf4917..3112cece4d1 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppIconService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppIconService.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update class AppIconService(private val localStorage: ILocalStorage) { - private val appIcons by lazy { AppIcon.values().asList() } + private val appIcons by lazy { AppIcon.entries } private val _optionsFlow = MutableStateFlow( Select(localStorage.appIcon ?: AppIcon.Main, appIcons) @@ -27,7 +27,7 @@ class AppIconService(private val localStorage: ILocalStorage) { val enabled = PackageManager.COMPONENT_ENABLED_STATE_ENABLED val disabled = PackageManager.COMPONENT_ENABLED_STATE_DISABLED - AppIcon.values().forEach { item -> + AppIcon.entries.forEach { item -> App.instance.packageManager.setComponentEnabledSetting( ComponentName(App.instance, item.launcherName), if (appIcon == item) enabled else disabled, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppearanceFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppearanceFragment.kt index daad2da5200..bc00b1dc6a6 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppearanceFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppearanceFragment.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -23,7 +22,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.ModalBottomSheetValue -import androidx.compose.material.Surface +import androidx.compose.material.Scaffold import androidx.compose.material.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -47,15 +46,12 @@ import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme import io.horizontalsystems.bankwallet.ui.compose.Select import io.horizontalsystems.bankwallet.ui.compose.components.AlertGroup import io.horizontalsystems.bankwallet.ui.compose.components.AppBar -import io.horizontalsystems.bankwallet.ui.compose.components.B2 import io.horizontalsystems.bankwallet.ui.compose.components.ButtonPrimaryTransparent import io.horizontalsystems.bankwallet.ui.compose.components.ButtonPrimaryYellow import io.horizontalsystems.bankwallet.ui.compose.components.CellUniversalLawrenceSection -import io.horizontalsystems.bankwallet.ui.compose.components.D1 import io.horizontalsystems.bankwallet.ui.compose.components.HeaderText import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.HsSwitch -import io.horizontalsystems.bankwallet.ui.compose.components.MultitextM1 import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal import io.horizontalsystems.bankwallet.ui.compose.components.TextImportantWarning import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer @@ -92,21 +88,22 @@ fun AppearanceScreen(navController: NavController) { var openBalanceValueSelector by rememberSaveable { mutableStateOf(false) } var openPriceChangeIntervalSelector by rememberSaveable { mutableStateOf(false) } - Surface(color = ComposeAppTheme.colors.tyler) { - ModalBottomSheetLayout( - sheetState = sheetState, - sheetBackgroundColor = ComposeAppTheme.colors.transparent, - sheetContent = { - AppCloseWarningBottomSheet( - onCloseClick = { scope.launch { sheetState.hide() } }, - onChangeClick = { - selectedAppIcon?.let { viewModel.onEnterAppIcon(it) } - scope.launch { sheetState.hide() } - } - ) - } - ) { - Column { + ModalBottomSheetLayout( + sheetState = sheetState, + sheetBackgroundColor = ComposeAppTheme.colors.transparent, + sheetContent = { + AppCloseWarningBottomSheet( + onCloseClick = { scope.launch { sheetState.hide() } }, + onChangeClick = { + selectedAppIcon?.let { viewModel.onEnterAppIcon(it) } + scope.launch { sheetState.hide() } + } + ) + } + ) { + Scaffold( + backgroundColor = ComposeAppTheme.colors.tyler, + topBar = { AppBar( title = stringResource(R.string.Settings_Appearance), navigationIcon = { @@ -114,160 +111,161 @@ fun AppearanceScreen(navController: NavController) { }, menuItems = listOf(), ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(paddingValues), + ) { + VSpacer(height = 12.dp) + CellUniversalLawrenceSection( + listOf { + MenuItemWithDialog( + R.string.Settings_Theme, + value = uiState.selectedTheme.title.getString(), + onClick = { openThemeSelector = true } + ) + } + ) - Column( - modifier = Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()), - ) { - VSpacer(height = 12.dp) - CellUniversalLawrenceSection( - listOf { + VSpacer(24.dp) + + HeaderText(text = stringResource(id = R.string.Appearance_MarketsTab)) + CellUniversalLawrenceSection( + listOf( + { + RowUniversal( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + body_leah( + text = stringResource(id = R.string.Appearance_HideMarketsTab), + modifier = Modifier + .weight(1f) + .padding(end = 16.dp) + ) + HsSwitch( + checked = uiState.marketsTabHidden, + onCheckedChange = { + viewModel.onSetMarketTabsHidden(it) + } + ) + } + }, + { MenuItemWithDialog( - R.string.Settings_Theme, - value = uiState.selectedTheme.title.getString(), - onClick = { openThemeSelector = true } + R.string.Appearance_PriceChangeInterval, + value = uiState.priceChangeInterval.title.getString(), + onClick = { openPriceChangeIntervalSelector = true } ) } ) + ) - VSpacer(24.dp) - - HeaderText(text = stringResource(id = R.string.Appearance_MarketsTab)) - CellUniversalLawrenceSection( - listOf ( - { - RowUniversal( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - body_leah( - text = stringResource(id = R.string.Appearance_HideMarketsTab), - modifier = Modifier - .weight(1f) - .padding(end = 16.dp) - ) - HsSwitch( - checked = uiState.marketsTabHidden, - onCheckedChange = { - viewModel.onSetMarketTabsHidden(it) - } - ) - } - }, - { + AnimatedVisibility(visible = !uiState.marketsTabHidden) { + Column { + VSpacer(32.dp) + CellUniversalLawrenceSection( + listOf { MenuItemWithDialog( - R.string.Appearance_PriceChangeInterval, - value = uiState.priceChangeInterval.title.getString(), - onClick = { openPriceChangeIntervalSelector = true } + R.string.Settings_LaunchScreen, + value = uiState.selectedLaunchScreen.title.getString(), + onClick = { openLaunchPageSelector = true } ) } ) - ) - - AnimatedVisibility(visible = !uiState.marketsTabHidden) { - Column { - VSpacer(32.dp) - CellUniversalLawrenceSection( - listOf { - MenuItemWithDialog( - R.string.Settings_LaunchScreen, - value = uiState.selectedLaunchScreen.title.getString(), - onClick = { openLaunchPageSelector = true } - ) - } - ) - } } + } - VSpacer(24.dp) - HeaderText(text = stringResource(id = R.string.Appearance_BalanceTab)) - CellUniversalLawrenceSection( - listOf( - { - RowUniversal( - modifier = Modifier.padding(horizontal = 16.dp), - ) { - body_leah( - text = stringResource(id = R.string.Appearance_HideBalanceTabButtons), - modifier = Modifier - .weight(1f) - .padding(end = 16.dp) - ) - HsSwitch( - checked = uiState.balanceTabButtonsHidden, - onCheckedChange = { - viewModel.onSetBalanceTabButtonsHidden(it) - } - ) - } - }, - { - MenuItemWithDialog( - R.string.Appearance_BalanceValue, - value = uiState.selectedBalanceViewType.title.getString(), - onClick = { openBalanceValueSelector = true } + VSpacer(24.dp) + HeaderText(text = stringResource(id = R.string.Appearance_BalanceTab)) + CellUniversalLawrenceSection( + listOf( + { + RowUniversal( + modifier = Modifier.padding(horizontal = 16.dp), + ) { + body_leah( + text = stringResource(id = R.string.Appearance_HideBalanceTabButtons), + modifier = Modifier + .weight(1f) + .padding(end = 16.dp) + ) + HsSwitch( + checked = uiState.balanceTabButtonsHidden, + onCheckedChange = { + viewModel.onSetBalanceTabButtonsHidden(it) + } ) } - ) + }, + { + MenuItemWithDialog( + R.string.Appearance_BalanceValue, + value = uiState.selectedBalanceViewType.title.getString(), + onClick = { openBalanceValueSelector = true } + ) + } ) + ) - VSpacer(24.dp) - HeaderText(text = stringResource(id = R.string.Appearance_AppIcon)) - AppIconSection(uiState.appIconOptions) { - scope.launch { - selectedAppIcon = it - sheetState.show() - } + VSpacer(24.dp) + HeaderText(text = stringResource(id = R.string.Appearance_AppIcon)) + AppIconSection(uiState.appIconOptions) { + scope.launch { + selectedAppIcon = it + sheetState.show() } - - VSpacer(32.dp) } + + VSpacer(32.dp) } - //Dialogs - if (openThemeSelector) { - AlertGroup( - R.string.Settings_Theme, - uiState.themeOptions, - { selected -> - viewModel.onEnterTheme(selected) - openThemeSelector = false - }, - { openThemeSelector = false } - ) - } - if (openLaunchPageSelector) { - AlertGroup( - R.string.Settings_LaunchScreen, - uiState.launchScreenOptions, - { selected -> - viewModel.onEnterLaunchPage(selected) - openLaunchPageSelector = false - }, - { openLaunchPageSelector = false } - ) - } - if (openBalanceValueSelector) { - AlertGroup( - R.string.Appearance_BalanceValue, - uiState.balanceViewTypeOptions, - { selected -> - viewModel.onEnterBalanceViewType(selected) - openBalanceValueSelector = false - }, - { openBalanceValueSelector = false } - ) - } - if (openPriceChangeIntervalSelector) { - AlertGroup( - R.string.Appearance_PriceChangeInterval, - uiState.priceChangeIntervalOptions, - { selected -> - viewModel.onSetPriceChangeInterval(selected) - openPriceChangeIntervalSelector = false - }, - { openPriceChangeIntervalSelector = false } - ) - } + } + //Dialogs + if (openThemeSelector) { + AlertGroup( + R.string.Settings_Theme, + uiState.themeOptions, + { selected -> + viewModel.onEnterTheme(selected) + openThemeSelector = false + }, + { openThemeSelector = false } + ) + } + if (openLaunchPageSelector) { + AlertGroup( + R.string.Settings_LaunchScreen, + uiState.launchScreenOptions, + { selected -> + viewModel.onEnterLaunchPage(selected) + openLaunchPageSelector = false + }, + { openLaunchPageSelector = false } + ) + } + if (openBalanceValueSelector) { + AlertGroup( + R.string.Appearance_BalanceValue, + uiState.balanceViewTypeOptions, + { selected -> + viewModel.onEnterBalanceViewType(selected) + openBalanceValueSelector = false + }, + { openBalanceValueSelector = false } + ) + } + if (openPriceChangeIntervalSelector) { + AlertGroup( + R.string.Appearance_PriceChangeInterval, + uiState.priceChangeIntervalOptions, + { selected -> + viewModel.onSetPriceChangeInterval(selected) + openPriceChangeIntervalSelector = false + }, + { openPriceChangeIntervalSelector = false } + ) } } } @@ -321,12 +319,13 @@ private fun AppIconSection(appIconOptions: Select, onAppIconSelect: (Ap AppIconsRow(rows[0], appIconOptions.selected, onAppIconSelect) AppIconsRow(rows[1], appIconOptions.selected, onAppIconSelect) AppIconsRow(rows[2], appIconOptions.selected, onAppIconSelect) + AppIconsRow(rows[3], appIconOptions.selected, onAppIconSelect) } } @Composable private fun AppIconsRow( - chunk: List, + chunk: List, selected: AppIcon, onAppIconSelect: (AppIcon) -> Unit ) { @@ -336,21 +335,19 @@ private fun AppIconsRow( .padding(horizontal = 14.dp), horizontalArrangement = Arrangement.SpaceBetween ) { - IconBox( - chunk[0].icon, - chunk[0].title.getString(), - chunk[0] == selected, - ) { onAppIconSelect(chunk[0]) } - IconBox( - chunk[1].icon, - chunk[1].title.getString(), - chunk[1] == selected, - ) { onAppIconSelect(chunk[1]) } - IconBox( - chunk[2].icon, - chunk[2].title.getString(), - chunk[2] == selected - ) { onAppIconSelect(chunk[2]) } + for (i in 0 until 3) { + val appIcon = chunk.getOrNull(i) + if (appIcon != null) { + IconBox( + appIcon.icon, + appIcon.title.getString(), + appIcon == selected + ) { onAppIconSelect(appIcon) } + } else { + // Invisible element to preserve space + Spacer(modifier = Modifier.size(60.dp)) + } + } } } @@ -388,60 +385,6 @@ private fun IconBox( } -@Composable -private fun RowMultilineSelect( - title: String, - subtitle: String, - selected: Boolean, - onClick: () -> Unit -) { - RowUniversal( - modifier = Modifier.padding(horizontal = 16.dp), - onClick = onClick - ) { - MultitextM1( - title = { B2(text = title) }, - subtitle = { D1(text = subtitle) } - ) - Spacer(modifier = Modifier.weight(1f)) - if (selected) { - Image( - painter = painterResource(id = R.drawable.ic_checkmark_20), - contentDescription = null, - colorFilter = ColorFilter.tint(ComposeAppTheme.colors.jacob) - ) - } - } -} - -@Composable -fun RowSelect( - imageContent: @Composable RowScope.() -> Unit, - text: String, - selected: Boolean, - onClick: () -> Unit -) { - RowUniversal( - modifier = Modifier.padding(horizontal = 16.dp), - onClick = onClick - ) { - imageContent.invoke(this) - body_leah( - text = text, - modifier = Modifier - .weight(1f) - .padding(horizontal = 16.dp) - ) - if (selected) { - Image( - painter = painterResource(id = R.drawable.ic_checkmark_20), - contentDescription = null, - colorFilter = ColorFilter.tint(ComposeAppTheme.colors.jacob) - ) - } - } -} - @Composable fun MenuItemWithDialog( @StringRes title: Int, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppearanceModule.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppearanceModule.kt index b7f8fe3f8ed..ee7748fdd15 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppearanceModule.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appearance/AppearanceModule.kt @@ -38,7 +38,9 @@ enum class AppIcon(val icon: Int, val titleText: String) : WithTranslatableTitle Yak(R.drawable.launcher_yak_preview, "Yak"), Punk(R.drawable.launcher_punk_preview, "Punk"), Ape(R.drawable.launcher_ape_preview, "#1874"), - Ball8(R.drawable.launcher_8ball_preview, "8ball"); + Ball8(R.drawable.launcher_8ball_preview, "8ball"), + Ivfun(R.drawable.launcher_ivfun_preview, "Ivfun"), + Duck(R.drawable.launcher_duck_preview, "Duck"); override val title: TranslatableString get() = TranslatableString.PlainString(titleText) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appstatus/AppStatusModule.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appstatus/AppStatusModule.kt index 6b3d483a781..a0922e829ab 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appstatus/AppStatusModule.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appstatus/AppStatusModule.kt @@ -19,6 +19,7 @@ object AppStatusModule { App.evmBlockchainManager, App.binanceKitManager, App.tronKitManager, + App.tonKitManager, App.solanaKitManager, App.btcBlockchainManager, ) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appstatus/AppStatusViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appstatus/AppStatusViewModel.kt index d10a8747b01..47de0975303 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appstatus/AppStatusViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/appstatus/AppStatusViewModel.kt @@ -15,6 +15,7 @@ import io.horizontalsystems.bankwallet.core.managers.BtcBlockchainManager import io.horizontalsystems.bankwallet.core.managers.EvmBlockchainManager import io.horizontalsystems.bankwallet.core.managers.MarketKitWrapper import io.horizontalsystems.bankwallet.core.managers.SolanaKitManager +import io.horizontalsystems.bankwallet.core.managers.TonKitManager import io.horizontalsystems.bankwallet.core.managers.TronKitManager import io.horizontalsystems.bankwallet.entities.Account import io.horizontalsystems.bankwallet.modules.settings.appstatus.AppStatusModule.BlockContent @@ -36,6 +37,7 @@ class AppStatusViewModel( private val evmBlockchainManager: EvmBlockchainManager, private val binanceKitManager: BinanceKitManager, private val tronKitManager: TronKitManager, + private val tonKitManager: TonKitManager, private val solanaKitManager: SolanaKitManager, private val btcBlockchainManager: BtcBlockchainManager, ) : ViewModelUiState() { @@ -203,6 +205,10 @@ class AppStatusViewModel( blockchainStatus["Tron"] = statusInfo } + tonKitManager.statusInfo?.let { statusInfo -> + blockchainStatus["Ton"] = statusInfo + } + solanaKitManager.statusInfo?.let { statusInfo -> blockchainStatus["Solana"] = statusInfo } @@ -214,13 +220,6 @@ class AppStatusViewModel( } } - walletManager.activeWallets.firstOrNull { it.token.blockchainType == BlockchainType.Ton } - ?.let { wallet -> - (adapterManager.getAdapterForWallet(wallet) as? TonAdapter)?.let { adapter -> - blockchainStatus["Ton"] = adapter.statusInfo - } - } - return blockchainStatus } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/main/MainSettingsScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/main/MainSettingsScreen.kt index 2fd8981e782..91d7c903828 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/main/MainSettingsScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/main/MainSettingsScreen.kt @@ -106,6 +106,21 @@ private fun SettingSections( VSpacer(32.dp) + CellUniversalLawrenceSection( + listOf { + HsSettingCell( + R.string.Settings_GetYourTokens, + R.drawable.ic_uwt2_24, + ComposeAppTheme.colors.jacob, + onClick = { + LinkHelper.openLinkInAppBrowser(context, "https://t.me/BeUnstoppable_bot") + } + ) + } + ) + + VSpacer(32.dp) + CellUniversalLawrenceSection( listOf({ HsSettingCell( diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoModule.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoModule.kt index 9cd2456f105..9823ceb791b 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoModule.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoModule.kt @@ -71,6 +71,6 @@ data class TransactionInfoItem( val BlockchainType.resendable: Boolean get() = when (this) { - BlockchainType.Optimism, BlockchainType.ArbitrumOne -> false + BlockchainType.Optimism, BlockchainType.Base, BlockchainType.ArbitrumOne -> false else -> true } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoService.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoService.kt index 31683b094db..3599d3da65a 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoService.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoService.kt @@ -76,7 +76,37 @@ class TransactionInfoService( val coinUids = mutableListOf() val txCoinTypes = when (val tx = transactionRecord) { - is TonTransactionRecord -> listOf(tx.mainValue.coinUid, tx.fee?.coinUid) + is TonTransactionRecord -> buildList { + add(tx.mainValue?.coinUid) + add(tx.fee.coinUid) + + tx.actions.forEach { action -> + val actionType = action.type + when (actionType) { + is TonTransactionRecord.Action.Type.Burn -> { + add(actionType.value.coinUid) + } + is TonTransactionRecord.Action.Type.ContractCall -> { + add(actionType.value.coinUid) + } + is TonTransactionRecord.Action.Type.Mint -> { + add(actionType.value.coinUid) + } + is TonTransactionRecord.Action.Type.Receive -> { + add(actionType.value.coinUid) + } + is TonTransactionRecord.Action.Type.Send -> { + add(actionType.value.coinUid) + } + is TonTransactionRecord.Action.Type.Swap -> { + add(actionType.valueIn.coinUid) + add(actionType.valueOut.coinUid) + } + is TonTransactionRecord.Action.Type.ContractDeploy, + is TonTransactionRecord.Action.Type.Unsupported -> Unit + } + } + } is EvmIncomingTransactionRecord -> listOf(tx.value.coinUid) is EvmOutgoingTransactionRecord -> listOf(tx.fee?.coinUid, tx.value.coinUid) is SwapTransactionRecord -> listOf(tx.fee, tx.valueIn, tx.valueOut).map { it?.coinUid } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoViewItemFactory.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoViewItemFactory.kt index 20374b88997..701b006ea08 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoViewItemFactory.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactionInfo/TransactionInfoViewItemFactory.kt @@ -69,7 +69,7 @@ class TransactionInfoViewItemFactory( private val evmLabelManager: EvmLabelManager, private val resendEnabled: Boolean, private val contactsRepo: ContactsRepository, - private val blockchainType: BlockchainType + private val blockchainType: BlockchainType, ) { private val zeroAddress = "0x0000000000000000000000000000000000000000" @@ -94,35 +94,115 @@ class TransactionInfoViewItemFactory( } is TonTransactionRecord -> { - when (transaction.type) { - TonTransactionRecord.Type.Incoming -> { - transaction.transfers.forEach { transfer -> - itemSections.add( + transaction.actions.forEach { action -> + val itemsForAction = mutableListOf() + + val actionType = action.type + when (actionType) { + is TonTransactionRecord.Action.Type.Send -> { + itemsForAction.addAll( + getSendSectionItems( + value = actionType.value, + toAddress = actionType.to, + coinPrice = rates[actionType.value.coinUid], + hideAmount = transactionItem.hideAmount, + sentToSelf = actionType.sentToSelf, + ) + ) + actionType.comment?.let { + itemsForAction.add( + Value( + getString(R.string.TransactionInfo_Memo), + it + ) + ) + } + } + + is TonTransactionRecord.Action.Type.Receive -> { + itemsForAction.addAll( getReceiveSectionItems( - value = transfer.amount, - fromAddress = transfer.src, - coinPrice = rates[transfer.amount.coinUid], + value = actionType.value, + fromAddress = actionType.from, + coinPrice = rates[actionType.value.coinUid], hideAmount = transactionItem.hideAmount, ) ) + actionType.comment?.let { + itemsForAction.add( + Value( + getString(R.string.TransactionInfo_Memo), + it + ) + ) + } } - } - TonTransactionRecord.Type.Outgoing -> { - transaction.transfers.forEach { transfer -> - itemSections.add( + + is TonTransactionRecord.Action.Type.Burn -> { + itemsForAction.addAll( getSendSectionItems( - value = transfer.amount, - toAddress = transfer.dest, - coinPrice = rates[transfer.amount.coinUid], + value = actionType.value, + toAddress = null, + coinPrice = rates[actionType.value.coinUid], + hideAmount = transactionItem.hideAmount + ) + ) + } + + is TonTransactionRecord.Action.Type.Mint -> { + itemsForAction.addAll( + getReceiveSectionItems( + value = actionType.value, + fromAddress = zeroAddress, + coinPrice = rates[actionType.value.coinUid], + hideAmount = transactionItem.hideAmount, + ) + ) + } + + is TonTransactionRecord.Action.Type.Swap -> { + itemsForAction.addAll( + getSwapEventSectionItems( + valueIn = actionType.valueIn, + valueOut = actionType.valueOut, + rates = rates, + amount = null, hideAmount = transactionItem.hideAmount, + hasRecipient = false ) ) } + + is TonTransactionRecord.Action.Type.ContractDeploy -> { +// case let .contractDeploy(interfaces): +// viewItems = [ +// .actionTitle(iconName: nil, iconDimmed: false, title: "transactions.contract_deploy".localized, subTitle: interfaces.joined(separator: ", ")), +// ] + } + + is TonTransactionRecord.Action.Type.ContractCall -> { +// case let .contractCall(address, value, operation): +// viewItems = [ +// .actionTitle(iconName: record.source.blockchainType.iconPlain32, iconDimmed: false, title: "transactions.contract_call".localized, subTitle: operation), +// .to(value: address, valueTitle: nil, contactAddress: nil) +// ] +// +// viewItems.append(contentsOf: sendSection(source: record.source, transactionValue: value, to: nil, rates: item.rates, balanceHidden: balanceHidden)) + } + + is TonTransactionRecord.Action.Type.Unsupported -> { + itemsForAction.add(Value("Action", actionType.type)) + } } - TonTransactionRecord.Type.Unknown -> { + + if (action.status == TransactionStatus.Failed) { + itemsForAction.add(Status(action.status)) } + + itemSections.add(itemsForAction) } - addMemoItem(transaction.memo, miscItemsSection) + +// feeViewItem = record.fee.map { .fee(title: "tx_info.fee".localized, value: feeString(transactionValue: $0, rate: _rate($0))) } } is EvmIncomingTransactionRecord -> itemSections.add( @@ -237,7 +317,13 @@ class TransactionInfoViewItemFactory( ) is ContractCallTransactionRecord -> { - itemSections.add(getContractMethodSectionItems(transaction.method, transaction.contractAddress, transaction.blockchainType)) + itemSections.add( + getContractMethodSectionItems( + transaction.method, + transaction.contractAddress, + transaction.blockchainType + ) + ) for (event in transaction.outgoingEvents) { itemSections.add( @@ -265,7 +351,13 @@ class TransactionInfoViewItemFactory( } is TronContractCallTransactionRecord -> { - itemSections.add(getContractMethodSectionItems(transaction.method, transaction.contractAddress, transaction.blockchainType)) + itemSections.add( + getContractMethodSectionItems( + transaction.method, + transaction.contractAddress, + transaction.blockchainType + ) + ) for (event in transaction.outgoingEvents) { itemSections.add( @@ -348,7 +440,8 @@ class TransactionInfoViewItemFactory( itemSections.add( listOf( Transaction( - transaction.transaction.contract?.label ?: getString(R.string.Transactions_ContractCall), + transaction.transaction.contract?.label + ?: getString(R.string.Transactions_ContractCall), "", TransactionViewItem.Icon.Platform(transaction.blockchainType).iconRes ) @@ -366,21 +459,33 @@ class TransactionInfoViewItemFactory( ) ) - miscItemsSection.addAll(getBitcoinSectionItems(transaction, transactionItem.lastBlockInfo)) + miscItemsSection.addAll( + getBitcoinSectionItems( + transaction, + transactionItem.lastBlockInfo + ) + ) addMemoItem(transaction.memo, miscItemsSection) } is BitcoinOutgoingTransactionRecord -> { sentToSelf = transaction.sentToSelf - itemSections.add(getSendSectionItems( - value = transaction.value, - toAddress = transaction.to, - coinPrice = rates[transaction.value.coinUid], - hideAmount = transactionItem.hideAmount, - sentToSelf = transaction.sentToSelf - )) - - miscItemsSection.addAll(getBitcoinSectionItems(transaction, transactionItem.lastBlockInfo)) + itemSections.add( + getSendSectionItems( + value = transaction.value, + toAddress = transaction.to, + coinPrice = rates[transaction.value.coinUid], + hideAmount = transactionItem.hideAmount, + sentToSelf = transaction.sentToSelf + ) + ) + + miscItemsSection.addAll( + getBitcoinSectionItems( + transaction, + transactionItem.lastBlockInfo + ) + ) addMemoItem(transaction.memo, miscItemsSection) } @@ -475,10 +580,24 @@ class TransactionInfoViewItemFactory( itemSections.add(getStatusSectionItems(transaction, status, rates)) if (transaction is EvmTransactionRecord && !transaction.foreignTransaction && status == TransactionStatus.Pending && resendEnabled) { - itemSections.add(listOf(SpeedUpCancel(transactionHash = transaction.transactionHash, blockchainType = transaction.blockchainType))) + itemSections.add( + listOf( + SpeedUpCancel( + transactionHash = transaction.transactionHash, + blockchainType = transaction.blockchainType + ) + ) + ) itemSections.add(listOf(TransactionInfoViewItem.Description(translator.getString(R.string.TransactionInfo_SpeedUpDescription)))) } else if (transaction is BitcoinOutgoingTransactionRecord && transaction.replaceable && resendEnabled) { - itemSections.add(listOf(SpeedUpCancel(transactionHash = transaction.transactionHash, blockchainType = transaction.blockchainType))) + itemSections.add( + listOf( + SpeedUpCancel( + transactionHash = transaction.transactionHash, + blockchainType = transaction.blockchainType + ) + ) + ) itemSections.add(listOf(TransactionInfoViewItem.Description(translator.getString(R.string.TransactionInfo_SpeedUpDescription)))) } itemSections.add(getExplorerSectionItems(transactionItem.explorerData)) @@ -487,12 +606,13 @@ class TransactionInfoViewItemFactory( } private fun getContact(address: String?): Contact? { - return contactsRepo.getContactsFiltered(blockchainType, addressQuery = address).firstOrNull() + return contactsRepo.getContactsFiltered(blockchainType, addressQuery = address) + .firstOrNull() } private fun addMemoItem( memo: String?, - miscItemsSection: MutableList + miscItemsSection: MutableList, ) { if (!memo.isNullOrBlank()) { miscItemsSection.add( @@ -506,10 +626,11 @@ class TransactionInfoViewItemFactory( fromAddress: String?, coinPrice: CurrencyValue?, hideAmount: Boolean, - nftMetadata: Map = mapOf() + nftMetadata: Map = mapOf(), ): List { val mint = fromAddress == zeroAddress - val title: String = if (mint) getString(R.string.Transactions_Mint) else getString(R.string.Transactions_Receive) + val title: String = + if (mint) getString(R.string.Transactions_Mint) else getString(R.string.Transactions_Receive) val amount: TransactionInfoViewItem val rate: TransactionInfoViewItem? @@ -533,7 +654,13 @@ class TransactionInfoViewItemFactory( if (!mint && fromAddress != null) { val contact = getContact(fromAddress) items.add( - Address(getString(R.string.TransactionInfo_From), fromAddress, contact == null, blockchainType, StatSection.AddressFrom) + Address( + getString(R.string.TransactionInfo_From), + fromAddress, + contact == null, + blockchainType, + StatSection.AddressFrom + ) ) contact?.let { items.add( @@ -553,23 +680,36 @@ class TransactionInfoViewItemFactory( coinPrice: CurrencyValue?, hideAmount: Boolean, sentToSelf: Boolean = false, - nftMetadata: Map = mapOf() + nftMetadata: Map = mapOf(), ): List { val burn = toAddress == zeroAddress - val title: String = if (burn) getString(R.string.Transactions_Burn) else getString(R.string.Transactions_Send) + val title: String = + if (burn) getString(R.string.Transactions_Burn) else getString(R.string.Transactions_Send) val amount: TransactionInfoViewItem val rate: TransactionInfoViewItem? when (value) { is TransactionValue.NftValue -> { - amount = getNftAmount(title, value, if (sentToSelf) null else false, hideAmount, nftMetadata[value.nftUid]) + amount = getNftAmount( + title, + value, + if (sentToSelf) null else false, + hideAmount, + nftMetadata[value.nftUid] + ) rate = null } else -> { - amount = getAmount(coinPrice, value, if (sentToSelf) null else false, hideAmount, AmountType.Sent) + amount = getAmount( + coinPrice, + value, + if (sentToSelf) null else false, + hideAmount, + AmountType.Sent + ) rate = getHistoricalRate(coinPrice, value) } } @@ -579,7 +719,13 @@ class TransactionInfoViewItemFactory( if (!burn && toAddress != null) { val contact = getContact(toAddress) items.add( - Address(getString(R.string.TransactionInfo_To), toAddress, contact == null, blockchainType, StatSection.AddressTo) + Address( + getString(R.string.TransactionInfo_To), + toAddress, + contact == null, + blockchainType, + StatSection.AddressTo + ) ) contact?.let { @@ -598,7 +744,7 @@ class TransactionInfoViewItemFactory( rates: Map, amount: SwapTransactionRecord.Amount?, hideAmount: Boolean, - hasRecipient: Boolean + hasRecipient: Boolean, ) = buildList { valueIn?.let { add( @@ -630,7 +776,7 @@ class TransactionInfoViewItemFactory( rates: Map, exchangeAddress: String, valueOut: TransactionValue?, - valueIn: TransactionValue? + valueIn: TransactionValue?, ): List { val items: MutableList = mutableListOf( Value( @@ -759,7 +905,13 @@ class TransactionInfoViewItemFactory( value.badge, AmountType.Approved ), - Address(getString(R.string.TransactionInfo_Spender), spenderAddress, contact == null, blockchainType, StatSection.AddressSpender) + Address( + getString(R.string.TransactionInfo_Spender), + spenderAddress, + contact == null, + blockchainType, + StatSection.AddressSpender + ) ) contact?.let { @@ -772,7 +924,7 @@ class TransactionInfoViewItemFactory( private fun getContractMethodSectionItems( method: String?, contractAddress: String, - blockchainType: BlockchainType + blockchainType: BlockchainType, ) = listOf( Transaction( method ?: getString(R.string.Transactions_ContractCall), @@ -781,7 +933,10 @@ class TransactionInfoViewItemFactory( ) ) - private fun getBitcoinSectionItems(transaction: BitcoinTransactionRecord, lastBlockInfo: LastBlockInfo?): List { + private fun getBitcoinSectionItems( + transaction: BitcoinTransactionRecord, + lastBlockInfo: LastBlockInfo?, + ): List { val items: MutableList = mutableListOf() transaction.conflictingHash?.let { conflictingHash -> @@ -808,10 +963,13 @@ class TransactionInfoViewItemFactory( private fun getStatusSectionItems( transaction: TransactionRecord, status: TransactionStatus, - rates: Map + rates: Map, ): List { val items: MutableList = mutableListOf( - Value(getString(R.string.TransactionInfo_Date), dateHelper.getFullDate(Date(transaction.timestamp * 1000))), + Value( + getString(R.string.TransactionInfo_Date), + dateHelper.getFullDate(Date(transaction.timestamp * 1000)) + ), Status(status) ) @@ -856,9 +1014,7 @@ class TransactionInfoViewItemFactory( } is TonTransactionRecord -> { - if (transaction.fee != null) { - items.add(getFeeItem(transaction.fee, rates[transaction.fee.coinUid], status)) - } + items.add(getFeeItem(transaction.fee, rates[transaction.fee.coinUid], status)) } is BitcoinOutgoingTransactionRecord -> @@ -882,12 +1038,16 @@ class TransactionInfoViewItemFactory( private fun getExplorerSectionItems(explorerData: TransactionInfoModule.ExplorerData): List = listOf( Explorer( - translator.getString(R.string.TransactionInfo_ButtonViewOnExplorerName, explorerData.title), + translator.getString( + R.string.TransactionInfo_ButtonViewOnExplorerName, + explorerData.title + ), explorerData.url ) ) - private fun getDoubleSpendViewItem(transactionHash: String, conflictingHash: String) = DoubleSpend(transactionHash, conflictingHash) + private fun getDoubleSpendViewItem(transactionHash: String, conflictingHash: String) = + DoubleSpend(transactionHash, conflictingHash) private fun getLockStateItem(lockState: TransactionLockState?): TransactionInfoViewItem? { return lockState?.let { @@ -931,20 +1091,23 @@ class TransactionInfoViewItemFactory( } } ?: "---" val fiatValueColored = ColoredValue(valueInFiat, ColorName.Grey) - val coinValueFormatted = if (hideAmount) "*****" else value.decimalValue?.let { decimalValue -> - val sign = when (incoming) { - true -> "+" - false -> "-" - else -> "" - } - val valueWithCoinCode = numberFormatter.formatCoinFull(decimalValue.abs(), value.coinCode, 8) - if (amount is SwapTransactionRecord.Amount.Extremum && incoming != null) { - val suffix = if (incoming) getString(R.string.Swap_AmountMin) else getString(R.string.Swap_AmountMax) - "$sign$valueWithCoinCode $suffix" - } else { - "$sign$valueWithCoinCode" - } - } ?: "---" + val coinValueFormatted = + if (hideAmount) "*****" else value.decimalValue?.let { decimalValue -> + val sign = when (incoming) { + true -> "+" + false -> "-" + else -> "" + } + val valueWithCoinCode = + numberFormatter.formatCoinFull(decimalValue.abs(), value.coinCode, 8) + if (amount is SwapTransactionRecord.Amount.Extremum && incoming != null) { + val suffix = + if (incoming) getString(R.string.Swap_AmountMin) else getString(R.string.Swap_AmountMax) + "$sign$valueWithCoinCode $suffix" + } else { + "$sign$valueWithCoinCode" + } + } ?: "---" val color = if (hasRecipient && incoming == true) { ColorName.Lucian @@ -975,7 +1138,7 @@ class TransactionInfoViewItemFactory( value: TransactionValue.NftValue, incoming: Boolean?, hideAmount: Boolean, - nftMetadata: NftAssetBriefMetadata? + nftMetadata: NftAssetBriefMetadata?, ): TransactionInfoViewItem { val valueFormatted = if (hideAmount) "*****" else value.decimalValue.let { decimalValue -> val sign = when { @@ -984,7 +1147,8 @@ class TransactionInfoViewItemFactory( decimalValue > BigDecimal.ZERO -> "+" else -> "" } - val valueWithCoinCode = numberFormatter.formatCoinFull(decimalValue.abs(), value.coinCode, 8) + val valueWithCoinCode = + numberFormatter.formatCoinFull(decimalValue.abs(), value.coinCode, 8) "$sign$valueWithCoinCode" } @@ -1022,7 +1186,10 @@ class TransactionInfoViewItemFactory( return Value(getString(R.string.TransactionInfo_HistoricalRate), rateValue) } - private fun getFee(transactionValue: TransactionValue, rate: CurrencyValue?): TransactionInfoViewItem { + private fun getFee( + transactionValue: TransactionValue, + rate: CurrencyValue?, + ): TransactionInfoViewItem { val feeAmountString = getFeeAmountString(rate, transactionValue) return Value(getString(R.string.TransactionInfo_Fee), feeAmountString) @@ -1031,20 +1198,24 @@ class TransactionInfoViewItemFactory( private fun getFeeItem( transactionValue: TransactionValue, rate: CurrencyValue?, - status: TransactionStatus + status: TransactionStatus, ): TransactionInfoViewItem { val feeAmountString = getFeeAmountString(rate, transactionValue) val feeTitle: String = when (status) { TransactionStatus.Pending -> getString(R.string.TransactionInfo_FeeEstimated) is TransactionStatus.Processing, TransactionStatus.Failed, - TransactionStatus.Completed -> getString(R.string.TransactionInfo_Fee) + TransactionStatus.Completed, + -> getString(R.string.TransactionInfo_Fee) } return Value(feeTitle, feeAmountString) } - private fun getFeeAmountString(rate: CurrencyValue?, transactionValue: TransactionValue): String { + private fun getFeeAmountString( + rate: CurrencyValue?, + transactionValue: TransactionValue, + ): String { val feeInFiat = rate?.let { transactionValue.decimalValue?.let { decimalValue -> numberFormatter.formatFiatFull( diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionRecordRepository.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionRecordRepository.kt index 160cfb90257..0772c7d6398 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionRecordRepository.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionRecordRepository.kt @@ -77,6 +77,7 @@ class TransactionRecordRepository( BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne, BlockchainType.Gnosis, BlockchainType.Fantom, diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionViewItemFactory.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionViewItemFactory.kt index 4016db4790e..6e895378044 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionViewItemFactory.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionViewItemFactory.kt @@ -3,12 +3,10 @@ package io.horizontalsystems.bankwallet.modules.transactions import io.horizontalsystems.bankwallet.R import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.bankwallet.core.adapters.TonTransactionRecord -import io.horizontalsystems.bankwallet.core.adapters.TonTransactionRecord.Type.Incoming -import io.horizontalsystems.bankwallet.core.adapters.TonTransactionRecord.Type.Outgoing -import io.horizontalsystems.bankwallet.core.adapters.TonTransactionRecord.Type.Unknown import io.horizontalsystems.bankwallet.core.managers.BalanceHiddenManager import io.horizontalsystems.bankwallet.core.managers.EvmLabelManager import io.horizontalsystems.bankwallet.core.providers.Translator +import io.horizontalsystems.bankwallet.core.shorten import io.horizontalsystems.bankwallet.entities.CurrencyValue import io.horizontalsystems.bankwallet.entities.TransactionValue import io.horizontalsystems.bankwallet.entities.nft.NftAssetBriefMetadata @@ -89,6 +87,7 @@ class TransactionViewItemFactory( is TransactionValue.CoinValue, is TransactionValue.RawValue, + is TransactionValue.JettonValue, is TransactionValue.TokenValue -> { TransactionViewItem.Icon.Regular(value.coinIconUrl, value.alternativeCoinIconUrl, value.coinIconPlaceholder) } @@ -118,6 +117,7 @@ class TransactionViewItemFactory( is TransactionValue.CoinValue, is TransactionValue.RawValue, + is TransactionValue.JettonValue, is TransactionValue.TokenValue -> { frontRectangle = false frontUrl = primaryValue.coinIconUrl @@ -147,6 +147,8 @@ class TransactionViewItemFactory( backAlternativeUrl = secondaryValue.alternativeCoinIconUrl backPlaceHolder = secondaryValue.coinIconPlaceholder } + + is TransactionValue.JettonValue -> TODO() } } else { backRectangle = false @@ -456,35 +458,79 @@ class TransactionViewItemFactory( getColoredValue(it, ColorName.Grey) } - val singleTransfer = record.transfers.singleOrNull() + val iconX: TransactionViewItem.Icon + var sentToSelf = false - when (record.type) { - Incoming -> { - title = Translator.getString(R.string.Transactions_Receive) - subtitle = if (singleTransfer == null) { - Translator.getString(R.string.Transactions_Multiple) - } else { - Translator.getString(R.string.Transactions_From, mapped(singleTransfer.src, record.blockchainType)) + val action = record.actions.singleOrNull() + + if (action != null) { + when (val actionType = action.type) { + is TonTransactionRecord.Action.Type.Send -> { + title = Translator.getString(R.string.Transactions_Send) + subtitle = Translator.getString(R.string.Transactions_To, mapped(actionType.to, record.blockchainType)) + + primaryValue = getColoredValue(actionType.value, ColorName.Lucian) + + sentToSelf = actionType.sentToSelf + + iconX = singleValueIconType(actionType.value) } + is TonTransactionRecord.Action.Type.Receive -> { + title = Translator.getString(R.string.Transactions_Receive) + subtitle = Translator.getString( + R.string.Transactions_From, + mapped(actionType.from, record.blockchainType) + ) - primaryValue = getColoredValue(record.mainValue, ColorName.Remus) - } - Outgoing -> { - title = Translator.getString(R.string.Transactions_Send) - subtitle = if (singleTransfer == null) { - Translator.getString(R.string.Transactions_Multiple) - } else { - Translator.getString(R.string.Transactions_To, mapped(singleTransfer.dest, record.blockchainType)) + primaryValue = getColoredValue(actionType.value, ColorName.Remus) + iconX = singleValueIconType(actionType.value) } + is TonTransactionRecord.Action.Type.Unsupported -> { + title = Translator.getString(R.string.Transactions_TonTransaction) + subtitle = actionType.type + primaryValue = null + secondaryValue = null - primaryValue = getColoredValue(record.mainValue, ColorName.Lucian) - } - Unknown -> { - title = Translator.getString(R.string.Transactions_Unknown) - subtitle = Translator.getString(R.string.Transactions_Unknown_Description) - primaryValue = null - secondaryValue = null + + iconX = TransactionViewItem.Icon.Platform(record.blockchainType) + } + is TonTransactionRecord.Action.Type.Burn -> { + iconX = singleValueIconType(actionType.value) + title = Translator.getString(R.string.Transactions_Burn) + subtitle = actionType.value.fullName + primaryValue = getColoredValue(actionType.value, ColorName.Lucian) + } + is TonTransactionRecord.Action.Type.ContractCall -> { + iconX = TransactionViewItem.Icon.Platform(record.blockchainType) + title = Translator.getString(R.string.Transactions_ContractCall) + subtitle = actionType.address.shorten() + primaryValue = getColoredValue(actionType.value, ColorName.Lucian) + } + is TonTransactionRecord.Action.Type.ContractDeploy -> { + iconX = TransactionViewItem.Icon.Platform(record.blockchainType) + title = Translator.getString(R.string.Transactions_ContractDeploy) + subtitle = actionType.interfaces.joinToString() + primaryValue = null + } + is TonTransactionRecord.Action.Type.Mint -> { + iconX = singleValueIconType(actionType.value) + title = Translator.getString(R.string.Transactions_Mint) + subtitle = actionType.value.fullName + primaryValue = getColoredValue(actionType.value, ColorName.Remus) + } + is TonTransactionRecord.Action.Type.Swap -> { + iconX = doubleValueIconType(actionType.valueOut, actionType.valueIn) + title = Translator.getString(R.string.Transactions_Swap) + subtitle = actionType.routerName ?: actionType.routerAddress.shorten() + primaryValue = getColoredValue(actionType.valueOut, ColorName.Remus) + secondaryValue = getColoredValue(actionType.valueIn, ColorName.Lucian) + } } + } else { + iconX = TransactionViewItem.Icon.Platform(record.blockchainType) + title = Translator.getString(R.string.Transactions_TonTransaction) + subtitle = Translator.getString(R.string.Transactions_Multiple) + primaryValue = null } return TransactionViewItem( @@ -495,8 +541,9 @@ class TransactionViewItemFactory( primaryValue = primaryValue, secondaryValue = secondaryValue, showAmount = showAmount, + sentToSelf = sentToSelf, date = Date(record.timestamp * 1000), - icon = icon ?: singleValueIconType(record.mainValue) + icon = icon ?: iconX ) } @@ -1053,6 +1100,7 @@ class TransactionViewItemFactory( is TransactionValue.CoinValue, is TransactionValue.RawValue, + is TransactionValue.JettonValue, is TransactionValue.TokenValue -> { currencyValue?.let { getColoredValue(it, ColorName.Grey) } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionsViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionsViewModel.kt index 3de1ea8d6ae..fdafeded16b 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionsViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/transactions/TransactionsViewModel.kt @@ -245,10 +245,12 @@ data class TransactionViewItem( BlockchainType.Polygon -> R.drawable.logo_chain_polygon_trx_24 BlockchainType.Avalanche -> R.drawable.logo_chain_avalanche_trx_24 BlockchainType.Optimism -> R.drawable.logo_chain_optimism_trx_24 + BlockchainType.Base -> R.drawable.logo_chain_base_trx_24 BlockchainType.ArbitrumOne -> R.drawable.logo_chain_arbitrum_one_trx_24 BlockchainType.Gnosis -> R.drawable.logo_chain_gnosis_trx_32 BlockchainType.Fantom -> R.drawable.logo_chain_fantom_trx_32 BlockchainType.Tron -> R.drawable.logo_chain_tron_trx_32 + BlockchainType.Ton -> R.drawable.logo_chain_ton_trx_32 else -> null } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/watchaddress/WatchAddressViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/watchaddress/WatchAddressViewModel.kt index 4a3b80857f7..d049a27b2ad 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/watchaddress/WatchAddressViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/watchaddress/WatchAddressViewModel.kt @@ -148,6 +148,7 @@ class WatchAddressViewModel( BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne, BlockchainType.Gnosis, BlockchainType.Fantom -> Type.EvmAddress diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/HSSwipeRefresh.kt b/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/HSSwipeRefresh.kt index 80a9a91b61a..ddba609d367 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/HSSwipeRefresh.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/HSSwipeRefresh.kt @@ -14,13 +14,14 @@ import androidx.compose.ui.Modifier @Composable fun HSSwipeRefresh( refreshing: Boolean, + modifier: Modifier = Modifier, onRefresh: () -> Unit, content: @Composable () -> Unit, ) { val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh) Box( - modifier = Modifier + modifier = modifier .fillMaxSize() .pullRefresh(pullRefreshState) ) { diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/components/TransactionInfoCells.kt b/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/components/TransactionInfoCells.kt index 8ccec6e2f56..06f477b1b5c 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/components/TransactionInfoCells.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/components/TransactionInfoCells.kt @@ -714,6 +714,7 @@ private fun openTransactionOptionsModule( BlockchainType.Polygon, BlockchainType.Avalanche, BlockchainType.Optimism, + BlockchainType.Base, BlockchainType.ArbitrumOne -> { navController.slideFromRight( R.id.transactionSpeedUpCancelFragment, diff --git a/app/src/main/res/drawable/base_erc20.xml b/app/src/main/res/drawable/base_erc20.xml new file mode 100644 index 00000000000..b7e07b08afe --- /dev/null +++ b/app/src/main/res/drawable/base_erc20.xml @@ -0,0 +1,27 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_uwt2_24.xml b/app/src/main/res/drawable/ic_uwt2_24.xml new file mode 100644 index 00000000000..c746b7ca850 --- /dev/null +++ b/app/src/main/res/drawable/ic_uwt2_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/launcher_duck_foreground.xml b/app/src/main/res/drawable/launcher_duck_foreground.xml new file mode 100644 index 00000000000..72e25142783 --- /dev/null +++ b/app/src/main/res/drawable/launcher_duck_foreground.xml @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/launcher_duck_preview.xml b/app/src/main/res/drawable/launcher_duck_preview.xml new file mode 100644 index 00000000000..6f38a27ede4 --- /dev/null +++ b/app/src/main/res/drawable/launcher_duck_preview.xml @@ -0,0 +1,421 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/launcher_ivfun_foreground.xml b/app/src/main/res/drawable/launcher_ivfun_foreground.xml new file mode 100644 index 00000000000..a07ab8992af --- /dev/null +++ b/app/src/main/res/drawable/launcher_ivfun_foreground.xml @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/launcher_ivfun_preview.xml b/app/src/main/res/drawable/launcher_ivfun_preview.xml new file mode 100644 index 00000000000..424cfd637d3 --- /dev/null +++ b/app/src/main/res/drawable/launcher_ivfun_preview.xml @@ -0,0 +1,358 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/logo_chain_base_trx_24.xml b/app/src/main/res/drawable/logo_chain_base_trx_24.xml new file mode 100644 index 00000000000..f36fc719691 --- /dev/null +++ b/app/src/main/res/drawable/logo_chain_base_trx_24.xml @@ -0,0 +1,13 @@ + + + + + + diff --git a/app/src/main/res/drawable/logo_chain_ton_trx_32.xml b/app/src/main/res/drawable/logo_chain_ton_trx_32.xml new file mode 100644 index 00000000000..b8701d6705c --- /dev/null +++ b/app/src/main/res/drawable/logo_chain_ton_trx_32.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/the_open_network_jetton.xml b/app/src/main/res/drawable/the_open_network_jetton.xml new file mode 100644 index 00000000000..4310b1873bb --- /dev/null +++ b/app/src/main/res/drawable/the_open_network_jetton.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher_duck.xml b/app/src/main/res/mipmap-anydpi-v26/launcher_duck.xml new file mode 100644 index 00000000000..6d38f53d2a4 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/launcher_duck.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher_ivfun.xml b/app/src/main/res/mipmap-anydpi-v26/launcher_ivfun.xml new file mode 100644 index 00000000000..53c1f17b6a3 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/launcher_ivfun.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/launcher_duck_background.xml b/app/src/main/res/values/launcher_duck_background.xml new file mode 100644 index 00000000000..0ac2789c90f --- /dev/null +++ b/app/src/main/res/values/launcher_duck_background.xml @@ -0,0 +1,4 @@ + + + #000000 + \ No newline at end of file diff --git a/app/src/main/res/values/launcher_ivfun_background.xml b/app/src/main/res/values/launcher_ivfun_background.xml new file mode 100644 index 00000000000..afaedca62c6 --- /dev/null +++ b/app/src/main/res/values/launcher_ivfun_background.xml @@ -0,0 +1,4 @@ + + + #000000 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d22f74b04ce..01e05fffc1b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -435,6 +435,7 @@ Set Amount Address type Locked + Processing Broadcasting Memo (Tag) Can\'t recognize @@ -472,6 +473,9 @@ This option allows you to resend a stuck transaction by increasing the amount of the fee. Enabled Disabled + Available Balance + Unsellect All + Sellect All Confirm Domain @@ -826,6 +830,7 @@ Contract Call External Call Contract Creation + Contract Deploy to %s from %s unlimited %s @@ -844,6 +849,7 @@ Swaps Approvals Unknown Transaction + Ton Transaction Transaction can not be parsed Multiple Filter @@ -927,6 +933,9 @@ Broadcast The transaction holding this amount is broadcasted but not yet accepted by the network. + Processing amount + Transactions with this amount still syncing. And when they are confirmed, these tokens will be available for spending + Double Spend Double Spend Risk! There is another transaction on the blockchain that is trying to spend inputs used in this transaction. Only one transaction will be accepted by the network This Tx @@ -989,6 +998,7 @@ Together, with your support, we can make this app even better! Donate + Get your tokens Donate %s Donate with Donation Address diff --git a/core/src/main/java/io/horizontalsystems/core/BackgroundManager.kt b/core/src/main/java/io/horizontalsystems/core/BackgroundManager.kt index f39097199f9..ff360275f1f 100644 --- a/core/src/main/java/io/horizontalsystems/core/BackgroundManager.kt +++ b/core/src/main/java/io/horizontalsystems/core/BackgroundManager.kt @@ -3,46 +3,31 @@ package io.horizontalsystems.core import android.app.Activity import android.app.Application import android.os.Bundle +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch class BackgroundManager(application: Application) : Application.ActivityLifecycleCallbacks { + private val scope = CoroutineScope(Dispatchers.Default) + private val _stateFlow: MutableSharedFlow = MutableSharedFlow() + val stateFlow: SharedFlow + get() = _stateFlow + init { application.registerActivityLifecycleCallbacks(this) } - interface Listener { - fun willEnterForeground(activity: Activity) {} - fun willEnterForeground() {} - fun didEnterBackground() {} - fun onAllActivitiesDestroyed() {} - } - private var foregroundActivityCount: Int = 0 private var aliveActivityCount: Int = 0 - private var listeners: MutableList = ArrayList() - - @Synchronized - fun registerListener(listener: Listener) { - listeners.add(listener) - } - - @Synchronized - fun unregisterListener(listener: Listener) { - listeners.remove(listener) - } - - val inForeground: Boolean - get() = foregroundActivityCount > 0 - - val inBackground: Boolean - get() = foregroundActivityCount == 0 @Synchronized override fun onActivityStarted(activity: Activity) { if (foregroundActivityCount == 0) { - listeners.forEach { listener -> - listener.willEnterForeground(activity) - listener.willEnterForeground() + scope.launch { + _stateFlow.emit(BackgroundManagerState.EnterForeground) } } foregroundActivityCount++ @@ -54,10 +39,9 @@ class BackgroundManager(application: Application) : Application.ActivityLifecycl if (foregroundActivityCount == 0) { //App is in background - listeners.forEach { listener -> - listener.didEnterBackground() + scope.launch { + _stateFlow.emit(BackgroundManagerState.EnterBackground) } - } } @@ -71,8 +55,8 @@ class BackgroundManager(application: Application) : Application.ActivityLifecycl aliveActivityCount-- if (aliveActivityCount == 0) { - listeners.forEach { listener -> - listener.onAllActivitiesDestroyed() + scope.launch { + _stateFlow.emit(BackgroundManagerState.AllActivitiesDestroyed) } } } @@ -84,3 +68,7 @@ class BackgroundManager(application: Application) : Application.ActivityLifecycl override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} } + +enum class BackgroundManagerState { + EnterForeground, EnterBackground, AllActivitiesDestroyed +} \ No newline at end of file diff --git a/core/src/main/java/io/horizontalsystems/core/Interfaces.kt b/core/src/main/java/io/horizontalsystems/core/Interfaces.kt index e0535e23822..26598e0305b 100644 --- a/core/src/main/java/io/horizontalsystems/core/Interfaces.kt +++ b/core/src/main/java/io/horizontalsystems/core/Interfaces.kt @@ -1,6 +1,5 @@ package io.horizontalsystems.core -import android.app.Activity import io.reactivex.Flowable import java.util.Date import javax.crypto.SecretKey @@ -37,7 +36,7 @@ interface IPinComponent { val isLocked: Boolean val pinSetFlowable: Flowable - fun willEnterForeground(activity: Activity) + fun willEnterForeground() fun didEnterBackground() fun setPin(pin: String) fun setDuressPin(pin: String)