From 38ab4b20294c87ecb1400d2962ebb9ff3374ce5c Mon Sep 17 00:00:00 2001 From: Pavneet-Sing Date: Wed, 15 Mar 2023 17:07:00 +0530 Subject: [PATCH] feat(wallet): Implemented show all networks assets - Implementing multi chain show balance (#27333) - Refactor network selector screen to show new "All network" (#27074) - Implemented multi-chain price and asset data collection (#27333) - Show overlapping network icon on Portfolio and Asset selection screens (#27333) - Show asset graph data for different networks and assets (#28748) - Fixed unwanted duplicate removal from list (#28506) - Fix balance issue for incoming asset - Reduce latency by separating P3A calls - Fixed edit visible asset to select assets - Fixed edit visible assets list for all network to show default assets in "edit visible assets" when all networks is selected (until all network is supported) - Updated portfolio, edit, view assets list UI list to show asset and network details from "All network" view - Fix AssetDetails activity to use passed network - Fixed AccountDetails activity issue with network - Tested and added available icons for networks/native assets, logged issue for missing assets (#28798) - Fix account list on asset details on AssetDetails screen - Fixed cached NFTs after unlock wallet (#28301) - Fixing asset graph data when multiple assets are shown - Fix nested network list is instantly closed after selection (click) and improve selection (#27614) - ERC721 NFTs with different token identifiers may show incorrect balance (#28627) - Implement All networks list sort order - Fix Buy, Send, Swap flows for selected multi-chain view token - Fix crash with test network with new flow - Ux: Show full network name on click on Edit visible via click popup - Clear balance with network switching - Clean up and refactoring to reuse --- .../chrome/browser/app/BraveActivity.java | 20 +- .../browser/app/domain/CryptoModel.java | 9 + .../browser/app/domain/CryptoSharedData.java | 6 + .../browser/app/domain/NetworkModel.java | 127 +++++++-- .../app/domain/NetworkSelectorModel.java | 66 +++-- .../browser/app/domain/PortfolioModel.java | 75 +++++- .../activities/AccountDetailActivity.java | 7 +- .../activities/AssetDetailActivity.java | 253 ++++++++---------- .../activities/BraveWalletBaseActivity.java | 2 - .../activities/BuySendSwapActivity.java | 121 +++++---- .../activities/NetworkSelectorActivity.java | 11 +- .../adapters/NetworkSpinnerAdapter.java | 20 +- .../adapters/WalletCoinAdapter.java | 108 +++----- ...isibleAssetsBottomSheetDialogFragment.java | 182 +++++++------ .../fragments/PortfolioFragment.java | 177 ++++++------ .../model/WalletListItemModel.java | 29 ++ .../observers/KeyringServiceObserverImpl.java | 54 ++-- .../crypto_wallet/util/AndroidUtils.java | 11 + .../crypto_wallet/util/AssetUtils.java | 13 + .../crypto_wallet/util/BalanceHelper.java | 2 +- .../browser/crypto_wallet/util/JavaUtils.java | 32 +++ .../crypto_wallet/util/NetworkUtils.java | 33 +++ .../crypto_wallet/util/PortfolioHelper.java | 185 ++++++++++--- .../crypto_wallet/util/TokenUtils.java | 27 +- .../browser/crypto_wallet/util/Utils.java | 200 ++++++++------ .../edit_visible_assets_bottom_sheet.xml | 57 ++-- .../java/res/layout/wallet_coin_list_item.xml | 34 ++- .../android/strings/android_brave_strings.grd | 3 + 28 files changed, 1200 insertions(+), 664 deletions(-) diff --git a/android/java/org/chromium/chrome/browser/app/BraveActivity.java b/android/java/org/chromium/chrome/browser/app/BraveActivity.java index 7f7dfbd42b44..ca4c0ede8627 100644 --- a/android/java/org/chromium/chrome/browser/app/BraveActivity.java +++ b/android/java/org/chromium/chrome/browser/app/BraveActivity.java @@ -6,6 +6,7 @@ package org.chromium.chrome.browser.app; import static org.chromium.chrome.browser.app.domain.NetworkSelectorModel.Mode.DEFAULT_WALLET_NETWORK; +import static org.chromium.chrome.browser.crypto_wallet.activities.NetworkSelectorActivity.NETWORK_SELECTOR_KEY; import static org.chromium.chrome.browser.crypto_wallet.activities.NetworkSelectorActivity.NETWORK_SELECTOR_MODE; import static org.chromium.ui.base.ViewUtils.dpToPx; @@ -1245,15 +1246,28 @@ public void viewOnBlockExplorer(String address, @CoinType.EnumType int coinType) // should only be called if the wallet is setup and unlocked public void openNetworkSelection() { - openNetworkSelection(DEFAULT_WALLET_NETWORK); + openNetworkSelection(DEFAULT_WALLET_NETWORK, null); } - // should only be called if the wallet is setup and unlocked - public void openNetworkSelection(NetworkSelectorModel.Mode mode) { + /** + * Open the network selector activity with key as an identifier to show the previously selected + * local network (if available otherwise All Networks as default) on {@link + * NetworkSelectorActivity}. + * @param mode Whether to open network selection for default/global network mode or + * in local network selection mode i.e. + * View <=> NetworkSelection state only with All Networks option. + * @param key as identifier to bind local state of NetworkSelection with the view. If null then + * use global/default network selection mode. + ^ IMP: Should only be called if the wallet is setup and unlocked + */ + public void openNetworkSelection(NetworkSelectorModel.Mode mode, String key) { Intent braveNetworkSelectionIntent = new Intent(this, NetworkSelectorActivity.class); braveNetworkSelectionIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // Either in global or local network selection mode braveNetworkSelectionIntent.putExtra(NETWORK_SELECTOR_MODE, mode); + // To bind selection between the caller and NetworkSelection Activity for local state of + // network selection + braveNetworkSelectionIntent.putExtra(NETWORK_SELECTOR_KEY, key); startActivity(braveNetworkSelectionIntent); } diff --git a/android/java/org/chromium/chrome/browser/app/domain/CryptoModel.java b/android/java/org/chromium/chrome/browser/app/domain/CryptoModel.java index 9ea9504f2819..b763903c157e 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/CryptoModel.java +++ b/android/java/org/chromium/chrome/browser/app/domain/CryptoModel.java @@ -51,6 +51,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; public class CryptoModel { private TxService mTxService; @@ -387,6 +388,14 @@ public List getSupportedCryptoAccountTypes() { return CryptoModel.this.getSupportedCryptoAccountTypes(); } + @Override + public List getSupportedCryptoCoins() { + return getSupportedCryptoAccountTypes() + .stream() + .map(CryptoAccountTypeInfo::getCoinType) + .collect(Collectors.toList()); + } + @Override public LiveData> getAccounts() { return mAccountInfosFromKeyRingModel; diff --git a/android/java/org/chromium/chrome/browser/app/domain/CryptoSharedData.java b/android/java/org/chromium/chrome/browser/app/domain/CryptoSharedData.java index 224899c33074..481fc01a8d50 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/CryptoSharedData.java +++ b/android/java/org/chromium/chrome/browser/app/domain/CryptoSharedData.java @@ -1,3 +1,8 @@ +/* Copyright (c) 2023 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + package org.chromium.chrome.browser.app.domain; import android.content.Context; @@ -16,5 +21,6 @@ public interface CryptoSharedData { LiveData getCoinTypeLd(); String[] getEnabledKeyrings(); List getSupportedCryptoAccountTypes(); + List getSupportedCryptoCoins(); LiveData> getAccounts(); } diff --git a/android/java/org/chromium/chrome/browser/app/domain/NetworkModel.java b/android/java/org/chromium/chrome/browser/app/domain/NetworkModel.java index e92f6f159fac..53714cccdf65 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/NetworkModel.java +++ b/android/java/org/chromium/chrome/browser/app/domain/NetworkModel.java @@ -10,6 +10,10 @@ import android.text.TextUtils; import android.util.Pair; +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LiveData; import androidx.lifecycle.MediatorLiveData; import androidx.lifecycle.MutableLiveData; @@ -24,6 +28,7 @@ import org.chromium.chrome.browser.crypto_wallet.model.CryptoAccountTypeInfo; import org.chromium.chrome.browser.crypto_wallet.util.JavaUtils; import org.chromium.chrome.browser.crypto_wallet.util.NetworkResponsesCollector; +import org.chromium.chrome.browser.crypto_wallet.util.NetworkUtils; import org.chromium.chrome.browser.crypto_wallet.util.Utils; import org.chromium.chrome.browser.crypto_wallet.util.WalletConstants; import org.chromium.chrome.browser.util.Triple; @@ -31,34 +36,39 @@ import org.chromium.mojo.system.MojoException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; public class NetworkModel implements JsonRpcServiceObserver { private JsonRpcService mJsonRpcService; private final Object mLock = new Object(); private final MediatorLiveData _mChainId; private final MediatorLiveData _mDefaultCoinCryptoNetworks; - private final MutableLiveData _mCryptoNetworks; + private final MutableLiveData> _mCryptoNetworks; private final CryptoSharedData mSharedData; private final CryptoSharedActions mCryptoActions; private final MediatorLiveData> _mPairChainAndNetwork; private final MediatorLiveData _mNeedToCreateAccountForNetwork; private final MediatorLiveData _mDefaultNetwork; - private final MediatorLiveData> + private final MediatorLiveData>> _mChainNetworkAllNetwork; private final Context mContext; private final MediatorLiveData _mCustomNetworkIds; private final MediatorLiveData> _mPrimaryNetworks; private final MediatorLiveData> _mSecondaryNetworks; - private NetworkSelectorModel mNetworkSelectorModel; + private Map mNetworkSelectorMap; public final LiveData mCustomNetworkIds; public LiveData mNeedToCreateAccountForNetwork; - public final LiveData> mChainNetworkAllNetwork; + public final LiveData>> mChainNetworkAllNetwork; public final LiveData> mPairChainAndNetwork; public final LiveData mChainId; public final LiveData mDefaultCoinCryptoNetworks; - public final LiveData mCryptoNetworks; + public final LiveData> mCryptoNetworks; public final LiveData mDefaultNetwork; public final LiveData> mPrimaryNetworks; public final LiveData> mSecondaryNetworks; @@ -74,7 +84,7 @@ public NetworkModel(JsonRpcService jsonRpcService, CryptoSharedData sharedData, _mDefaultCoinCryptoNetworks = new MediatorLiveData<>(); mChainId = _mChainId; mDefaultCoinCryptoNetworks = _mDefaultCoinCryptoNetworks; - _mCryptoNetworks = new MutableLiveData<>(new NetworkInfo[0]); + _mCryptoNetworks = new MutableLiveData<>(Collections.emptyList()); mCryptoNetworks = _mCryptoNetworks; _mPairChainAndNetwork = new MediatorLiveData<>(); mPairChainAndNetwork = _mPairChainAndNetwork; @@ -92,7 +102,7 @@ public NetworkModel(JsonRpcService jsonRpcService, CryptoSharedData sharedData, _mSecondaryNetworks = new MediatorLiveData<>(); mSecondaryNetworks = _mSecondaryNetworks; jsonRpcService.addObserver(this); - mNetworkSelectorModel = new NetworkSelectorModel(this, mContext); + mNetworkSelectorMap = new HashMap<>(); _mPairChainAndNetwork.setValue(Pair.create("", new NetworkInfo[] {})); _mPairChainAndNetwork.addSource(_mChainId, chainId -> { _mPairChainAndNetwork.setValue( @@ -106,8 +116,7 @@ public NetworkModel(JsonRpcService jsonRpcService, CryptoSharedData sharedData, NetworkInfo[] cryptoNetworks = chainIdAndInfosPair.second; if (chainId == null || cryptoNetworks == null) return; for (NetworkInfo networkInfo : cryptoNetworks) { - if (networkInfo.chainId.equals(chainId) - && sharedData.getCoinType() == networkInfo.coin) { + if (networkInfo.chainId.equals(chainId)) { _mDefaultNetwork.postValue(networkInfo); break; } @@ -131,11 +140,19 @@ public NetworkModel(JsonRpcService jsonRpcService, CryptoSharedData sharedData, }); _mChainNetworkAllNetwork.addSource(_mDefaultNetwork, networkInfo -> { + NetworkInfo currNetwork = null; + if (_mChainNetworkAllNetwork.getValue() != null) { + currNetwork = _mChainNetworkAllNetwork.getValue().second; + } + if (currNetwork != null && networkInfo != null + && NetworkUtils.Filters.isSameNetwork(currNetwork, networkInfo)) { + return; + } _mChainNetworkAllNetwork.postValue( Triple.create(networkInfo.chainId, networkInfo, _mCryptoNetworks.getValue())); }); _mChainNetworkAllNetwork.addSource(_mCryptoNetworks, networkInfos -> { - String chainId = _mChainId.getValue(); + String chainId = null; NetworkInfo networkInfo = _mDefaultNetwork.getValue(); if (networkInfo != null) { chainId = networkInfo.chainId; @@ -172,12 +189,43 @@ public NetworkModel(JsonRpcService jsonRpcService, CryptoSharedData sharedData, * @return mNetworkSelectorModel object in DEFAULT_WALLET_NETWORK mode */ public NetworkSelectorModel openNetworkSelectorModel() { - return openNetworkSelectorModel(NetworkSelectorModel.Mode.DEFAULT_WALLET_NETWORK); + return new NetworkSelectorModel(this, mContext); } - public NetworkSelectorModel openNetworkSelectorModel(NetworkSelectorModel.Mode mode) { - mNetworkSelectorModel.updateSelectorMode(mode); - return mNetworkSelectorModel; + /** + * Create a network model to handle either default or local state (onscreen e.g. {@link + * org.chromium.chrome.browser.crypto_wallet.fragments.PortfolioFragment}). Local network + * selection can be used by many views so make sure to use the same key which acts as a contract + * between the view and the selection activity. + * @param key acts as a binding key between caller and selection activity. + * @param mode to handle network selection event globally or locally. + * @param lifecycle to auto remove network-selection objects. + * @return NetworkSelectorModel object. + */ + public NetworkSelectorModel openNetworkSelectorModel( + String key, NetworkSelectorModel.Mode mode, Lifecycle lifecycle) { + NetworkSelectorModel networkSelectorModel; + if (key == null) { + return new NetworkSelectorModel(mode, this, mContext); + } else if (mNetworkSelectorMap.containsKey(key)) { + // Use existing NetworkSelector object to show the previously selected network + networkSelectorModel = mNetworkSelectorMap.get(key); + if (networkSelectorModel != null && mode != networkSelectorModel.getMode()) { + networkSelectorModel.updateSelectorMode(mode); + } + } else { + networkSelectorModel = new NetworkSelectorModel(mode, this, mContext); + mNetworkSelectorMap.put(key, networkSelectorModel); + } + if (lifecycle != null) { + lifecycle.addObserver(new DefaultLifecycleObserver() { + @Override + public void onDestroy(@NonNull LifecycleOwner owner) { + mNetworkSelectorMap.remove(key); + } + }); + } + return networkSelectorModel; } public void setAccountInfosFromKeyRingModel( @@ -212,22 +260,27 @@ public void refreshNetworks() { init(); } + static void getAllNetworks(JsonRpcService jsonRpcService, List supportedCoins, + Callbacks.Callback1> callback) { + if (jsonRpcService == null) { + callback.call(Collections.emptySet()); + return; + } + + NetworkResponsesCollector networkResponsesCollector = + new NetworkResponsesCollector(jsonRpcService, supportedCoins); + networkResponsesCollector.getNetworks(networkInfos -> { callback.call(networkInfos); }); + } + public void init() { synchronized (mLock) { if (mJsonRpcService == null) { return; } - List coins = new ArrayList<>(); - for (CryptoAccountTypeInfo cryptoAccountTypeInfo : - mSharedData.getSupportedCryptoAccountTypes()) { - coins.add(cryptoAccountTypeInfo.getCoinType()); - } - NetworkResponsesCollector networkResponsesCollector = - new NetworkResponsesCollector(mJsonRpcService, coins); - networkResponsesCollector.getNetworks(networkInfos -> { - _mCryptoNetworks.postValue( - stripDebugNetwork(mContext, networkInfos.toArray(new NetworkInfo[0]))); + getAllNetworks(mJsonRpcService, mSharedData.getSupportedCryptoCoins(), allNetworks -> { + _mCryptoNetworks.postValue(Arrays.asList( + stripDebugNetwork(mContext, allNetworks.toArray(new NetworkInfo[0])))); }); } } @@ -250,6 +303,11 @@ public void setNetworkWithAccountCheck( } } + public void setNetworkWithAccountCheck(String chainId, Callbacks.Callback1 callback) { + NetworkInfo networkToBeSetAsSelected = getNetwork(chainId); + setNetworkWithAccountCheck(networkToBeSetAsSelected, callback); + } + public void setNetwork( NetworkInfo networkToBeSetAsSelected, Callbacks.Callback1 callback) { mJsonRpcService.setNetwork( @@ -268,8 +326,8 @@ public void clearCreateAccountState() { _mNeedToCreateAccountForNetwork.postValue(null); } - public NetworkInfo[] stripNoBuySwapNetworks( - NetworkInfo[] networkInfos, BuySendSwapActivity.ActivityType type) { + public List stripNoBuySwapNetworks( + List networkInfos, BuySendSwapActivity.ActivityType type) { List networkInfosFiltered = new ArrayList<>(); for (NetworkInfo networkInfo : networkInfos) { if (type == BuySendSwapActivity.ActivityType.BUY && Utils.allowBuy(networkInfo.chainId) @@ -279,7 +337,18 @@ public NetworkInfo[] stripNoBuySwapNetworks( } } - return networkInfosFiltered.toArray(new NetworkInfo[networkInfosFiltered.size()]); + return networkInfosFiltered; + } + + public NetworkInfo getNetwork(String chainId) { + if (TextUtils.isEmpty(chainId)) return null; + List cryptoNws = JavaUtils.safeVal(_mCryptoNetworks.getValue()); + for (NetworkInfo info : cryptoNws) { + if (info.chainId.equals(chainId)) { + return info; + } + } + return null; } private NetworkInfo[] stripDebugNetwork(Context context, NetworkInfo[] networkInfos) { @@ -298,8 +367,8 @@ private NetworkInfo[] stripDebugNetwork(Context context, NetworkInfo[] networkIn } List getSubTestNetworks(NetworkInfo networkInfo) { - NetworkInfo[] cryptoNws = _mCryptoNetworks.getValue(); - if (cryptoNws == null || cryptoNws.length == 0 + List cryptoNws = _mCryptoNetworks.getValue(); + if (cryptoNws == null || cryptoNws.size() == 0 || !WalletConstants.SUPPORTED_TOP_LEVEL_CHAIN_IDS.contains(networkInfo.chainId)) return Collections.emptyList(); List list = new ArrayList<>(); diff --git a/android/java/org/chromium/chrome/browser/app/domain/NetworkSelectorModel.java b/android/java/org/chromium/chrome/browser/app/domain/NetworkSelectorModel.java index facfccae76d2..b2ff63d9d065 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/NetworkSelectorModel.java +++ b/android/java/org/chromium/chrome/browser/app/domain/NetworkSelectorModel.java @@ -30,21 +30,22 @@ public class NetworkSelectorModel { private final Context mContext; private final NetworkModel mNetworkModel; - private final MutableLiveData _mLocalSelectedNetwork; + private final MutableLiveData _mSelectedNetwork; private Mode mMode; public LiveData> mPrimaryNetworks; public LiveData> mSecondaryNetworks; - public final LiveData mSelectedNetwork; + private final LiveData mSelectedNetwork; + private String mSelectedChainId; public NetworkSelectorModel(Mode mode, NetworkModel networkModel, Context context) { mMode = mode; if (mMode == null) { - mMode = Mode.LOCAL_NETWORK_FILTER; + mMode = Mode.DEFAULT_WALLET_NETWORK; } mNetworkModel = networkModel; mContext = context; - _mLocalSelectedNetwork = new MutableLiveData<>(); - mSelectedNetwork = _mLocalSelectedNetwork; + _mSelectedNetwork = new MutableLiveData<>(); + mSelectedNetwork = _mSelectedNetwork; init(); } @@ -54,9 +55,11 @@ public NetworkSelectorModel(NetworkModel networkModel, Context context) { public void init() { if (mMode == Mode.DEFAULT_WALLET_NETWORK) { - _mLocalSelectedNetwork.postValue(mNetworkModel.mDefaultNetwork.getValue()); + _mSelectedNetwork.postValue(mNetworkModel.mDefaultNetwork.getValue()); } else if (mMode == Mode.LOCAL_NETWORK_FILTER) { - _mLocalSelectedNetwork.postValue(NetworkUtils.getAllNetworkOption(mContext)); + if (mSelectedChainId == null) { + _mSelectedNetwork.postValue(NetworkUtils.getAllNetworkOption(mContext)); + } } mPrimaryNetworks = Transformations.map(mNetworkModel.mPrimaryNetworks, networkInfos -> { List list = new ArrayList<>(); @@ -69,6 +72,7 @@ public void init() { list.add(new NetworkInfoPresenter( networkInfo, true, mNetworkModel.getSubTestNetworks(networkInfo))); } + updateLocalNetwork(networkInfos, mSelectedChainId); return list; }); mSecondaryNetworks = Transformations.map(mNetworkModel.mSecondaryNetworks, networkInfos -> { @@ -76,6 +80,7 @@ public void init() { for (NetworkInfo networkInfo : networkInfos) { list.add(new NetworkInfoPresenter(networkInfo, false, Collections.emptyList())); } + updateLocalNetwork(networkInfos, mSelectedChainId); return list; }); } @@ -93,23 +98,46 @@ public void updateSelectorMode(Mode mode) { public void setNetworkWithAccountCheck( NetworkInfo networkToBeSetAsSelected, Callbacks.Callback1 callback) { - boolean hasAccountOfNetworkType = - mNetworkModel.hasAccountOfNetworkType(networkToBeSetAsSelected); - if (!hasAccountOfNetworkType && mMode == Mode.DEFAULT_WALLET_NETWORK) { - // Delegate to network model to handle account creation flow - mNetworkModel.setNetworkWithAccountCheck(networkToBeSetAsSelected, isSet -> { - callback.call(isSet); - if (isSet) { - _mLocalSelectedNetwork.postValue(networkToBeSetAsSelected); - } - }); - return; + // Default/Global wallet network does not support "All Networks" + if (!networkToBeSetAsSelected.chainId.equals( + NetworkUtils.getAllNetworkOption(mContext).chainId)) { + boolean hasAccountOfNetworkType = + mNetworkModel.hasAccountOfNetworkType(networkToBeSetAsSelected); + if (!hasAccountOfNetworkType || mMode == Mode.DEFAULT_WALLET_NETWORK) { + // Delegate to network model to handle account creation flow if required + mNetworkModel.setNetworkWithAccountCheck(networkToBeSetAsSelected, isSet -> { + callback.call(isSet); + if (isSet) { + _mSelectedNetwork.postValue(networkToBeSetAsSelected); + } + }); + return; + } } if (mMode == Mode.LOCAL_NETWORK_FILTER) { - _mLocalSelectedNetwork.postValue(networkToBeSetAsSelected); + _mSelectedNetwork.postValue(networkToBeSetAsSelected); callback.call(true); } } + public LiveData getSelectedNetwork() { + if (mMode == Mode.DEFAULT_WALLET_NETWORK) { + return mNetworkModel.mDefaultNetwork; + } else { + return mSelectedNetwork; + } + } + + public Mode getMode() { + return mMode; + } + + private void updateLocalNetwork(List networkInfos, String chainId) { + NetworkInfo networkInfo = NetworkUtils.findNetwork(networkInfos, chainId); + if (networkInfo != null) { + _mSelectedNetwork.postValue(networkInfo); + } + } + public enum Mode { DEFAULT_WALLET_NETWORK, LOCAL_NETWORK_FILTER } } diff --git a/android/java/org/chromium/chrome/browser/app/domain/PortfolioModel.java b/android/java/org/chromium/chrome/browser/app/domain/PortfolioModel.java index c4dcdee657c9..cb589bc1ab8d 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/PortfolioModel.java +++ b/android/java/org/chromium/chrome/browser/app/domain/PortfolioModel.java @@ -14,6 +14,7 @@ import org.json.JSONException; import org.json.JSONObject; +import org.chromium.brave_wallet.mojom.AccountInfo; import org.chromium.brave_wallet.mojom.AssetRatioService; import org.chromium.brave_wallet.mojom.BlockchainRegistry; import org.chromium.brave_wallet.mojom.BlockchainToken; @@ -25,24 +26,29 @@ import org.chromium.brave_wallet.mojom.NetworkInfo; import org.chromium.brave_wallet.mojom.SolanaTxManagerProxy; import org.chromium.brave_wallet.mojom.TxService; +import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletBaseActivity; import org.chromium.chrome.browser.crypto_wallet.observers.BraveWalletServiceObserverImpl; import org.chromium.chrome.browser.crypto_wallet.observers.BraveWalletServiceObserverImpl.BraveWalletServiceObserverImplDelegate; +import org.chromium.chrome.browser.crypto_wallet.observers.KeyringServiceObserverImpl; import org.chromium.chrome.browser.crypto_wallet.util.AssetUtils; import org.chromium.chrome.browser.crypto_wallet.util.AsyncUtils; import org.chromium.chrome.browser.crypto_wallet.util.JavaUtils; +import org.chromium.chrome.browser.crypto_wallet.util.NetworkUtils; import org.chromium.chrome.browser.crypto_wallet.util.PortfolioHelper; +import org.chromium.chrome.browser.crypto_wallet.util.WalletConstants; +import org.chromium.chrome.browser.crypto_wallet.util.WalletUtils; +import org.chromium.mojo.bindings.Callbacks; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; public class PortfolioModel implements BraveWalletServiceObserverImplDelegate { public final LiveData> mNftModels; private final CryptoSharedData mSharedData; - private final MutableLiveData> _mNftModels; private final Object mLock = new Object(); - public PortfolioHelper mPortfolioHelper; private TxService mTxService; private KeyringService mKeyringService; private BlockchainRegistry mBlockchainRegistry; @@ -52,8 +58,11 @@ public class PortfolioModel implements BraveWalletServiceObserverImplDelegate { private BraveWalletService mBraveWalletService; private AssetRatioService mAssetRatioService; private Context mContext; + private final MutableLiveData> _mNftModels; private MutableLiveData _mIsDiscoveringUserAssets; public LiveData mIsDiscoveringUserAssets; + private List mAllNetworkInfos; + public PortfolioHelper mPortfolioHelper; public PortfolioModel(Context context, TxService txService, KeyringService keyringService, BlockchainRegistry blockchainRegistry, JsonRpcService jsonRpcService, @@ -132,6 +141,63 @@ public void discoverAssetsOnAllSupportedChains() { mBraveWalletService.discoverAssetsOnAllSupportedChains(); } + public void fetchAssets(NetworkInfo selectedNetwork, + BraveWalletBaseActivity braveWalletBaseActivity, + Callbacks.Callback1 callback) { + synchronized (mLock) { + clear(); + if (mJsonRpcService == null) { + return; + } + NetworkModel.getAllNetworks( + mJsonRpcService, mSharedData.getSupportedCryptoCoins(), allNetworks -> { + mAllNetworkInfos = new ArrayList<>(allNetworks); + if (selectedNetwork.chainId.equals( + NetworkUtils.getAllNetworkOption(mContext).chainId)) { + mKeyringService.getKeyringsInfo( + mSharedData.getEnabledKeyrings(), keyringInfos -> { + AccountInfo[] accountInfos = + WalletUtils + .getAccountInfosFromKeyrings(keyringInfos) + .toArray(new AccountInfo[0]); + mPortfolioHelper = + new PortfolioHelper(braveWalletBaseActivity, + mAllNetworkInfos, accountInfos); + mPortfolioHelper.setSelectedNetworks( + NetworkUtils.nonTestNetwork(mAllNetworkInfos)); + fetchUserAssetsAndDetails(mPortfolioHelper, callback); + }); + } else { + mKeyringService.getKeyringInfo( + AssetUtils.getKeyringForCoinType(selectedNetwork.coin), + keyringInfo -> { + mPortfolioHelper = + new PortfolioHelper(braveWalletBaseActivity, + mAllNetworkInfos, keyringInfo.accountInfos); + mPortfolioHelper.setSelectedNetworks( + Arrays.asList(selectedNetwork)); + fetchUserAssetsAndDetails(mPortfolioHelper, callback); + }); + } + }); + } + } + + @Override + public void onDiscoverAssetsCompleted(BlockchainToken[] discoveredAssets) { + _mIsDiscoveringUserAssets.postValue(false); + } + + // Clear state + public void clear() { + _mNftModels.postValue(Collections.emptyList()); + } + + private void fetchUserAssetsAndDetails( + PortfolioHelper portfolioHelper, Callbacks.Callback1 callback) { + portfolioHelper.fetchAllAssetsAndDetails(callback); + } + private void addServiceObservers() { if (mBraveWalletService != null) { BraveWalletServiceObserverImpl walletServiceObserver = @@ -205,9 +271,4 @@ private String refineUrl(String url) { return AssetUtils.httpifyIpfsUrl(url); } } - - @Override - public void onDiscoverAssetsCompleted(BlockchainToken[] discoveredAssets) { - _mIsDiscoveringUserAssets.postValue(false); - } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/AccountDetailActivity.java b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/AccountDetailActivity.java index e6b9399e43fe..427587f37c2e 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/AccountDetailActivity.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/AccountDetailActivity.java @@ -152,7 +152,7 @@ private void setUpAssetList(NetworkInfo selectedNetwork) { portfolioHelper.calculateBalances(() -> { RecyclerView rvAssets = findViewById(R.id.rv_assets); - BlockchainToken[] userAssets = portfolioHelper.getUserAssets(); + List userAssets = portfolioHelper.getUserAssetList(); HashMap perTokenCryptoSum = portfolioHelper.getPerTokenCryptoSum(); HashMap perTokenFiatSum = @@ -161,8 +161,9 @@ private void setUpAssetList(NetworkInfo selectedNetwork) { String tokensPath = BlockchainRegistryFactory.getInstance().getTokensIconsLocation(); - WalletCoinAdapter walletCoinAdapter = Utils.setupVisibleAssetList( - userAssets, perTokenCryptoSum, perTokenFiatSum, tokensPath); + WalletCoinAdapter walletCoinAdapter = + Utils.setupVisibleAssetList(userAssets, perTokenCryptoSum, + perTokenFiatSum, tokensPath, getResources(), allNetworks); walletCoinAdapter.setOnWalletListItemClick(AccountDetailActivity.this); rvAssets.setAdapter(walletCoinAdapter); rvAssets.setLayoutManager(new LinearLayoutManager(this)); diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/AssetDetailActivity.java b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/AssetDetailActivity.java index 60eb2eb4533b..c4c27a220e51 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/AssetDetailActivity.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/AssetDetailActivity.java @@ -39,6 +39,7 @@ import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.JsonRpcService; import org.chromium.brave_wallet.mojom.KeyringService; +import org.chromium.brave_wallet.mojom.NetworkInfo; import org.chromium.brave_wallet.mojom.TransactionInfo; import org.chromium.chrome.R; import org.chromium.chrome.browser.app.BraveActivity; @@ -51,6 +52,7 @@ import org.chromium.chrome.browser.crypto_wallet.observers.ApprovedTxObserver; import org.chromium.chrome.browser.crypto_wallet.util.AndroidUtils; import org.chromium.chrome.browser.crypto_wallet.util.AssetUtils; +import org.chromium.chrome.browser.crypto_wallet.util.JavaUtils; import org.chromium.chrome.browser.crypto_wallet.util.SmoothLineChartEquallySpaced; import org.chromium.chrome.browser.crypto_wallet.util.TokenUtils; import org.chromium.chrome.browser.crypto_wallet.util.Utils; @@ -61,6 +63,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -93,12 +96,17 @@ public class AssetDetailActivity private boolean mNativeInitialized; private boolean mShouldShowDialog; private WalletModel mWalletModel; + private NetworkInfo mAssetNetwork; @Override protected void triggerLayoutInflation() { setContentView(R.layout.activity_asset_detail); - String chainId = ""; + BraveActivity activity = BraveActivity.getBraveActivity(); + if (activity != null) { + mWalletModel = activity.getWalletModel(); + } + if (getIntent() != null) { mChainId = getIntent().getStringExtra(Utils.CHAIN_ID); mAssetSymbol = getIntent().getStringExtra(Utils.ASSET_SYMBOL); @@ -109,21 +117,16 @@ protected void triggerLayoutInflation() { mAssetDecimals = getIntent().getIntExtra(Utils.ASSET_DECIMALS, Utils.ETH_DEFAULT_DECIMALS); mCoinType = getIntent().getIntExtra(Utils.COIN_TYPE, CoinType.ETH); - - getBlockchainToken(() -> {}); - if (mAssetSymbol.equals("ETH")) { mAssetLogo = "eth.png"; } + + mAssetNetwork = mWalletModel.getCryptoModel().getNetworkModel().getNetwork(mChainId); + + getBlockchainToken(() -> {}); } mExecutor = Executors.newSingleThreadExecutor(); mHandler = new Handler(Looper.getMainLooper()); - try { - BraveActivity activity = BraveActivity.getBraveActivity(); - mWalletModel = activity.getWalletModel(); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "triggerLayoutInflation " + e); - } Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -151,13 +154,13 @@ mAssetSymbol, getResources().getDisplayMetrics().density, assetTitleText, this, @Override public void onClick(View v) { Utils.openBuySendSwapActivity(AssetDetailActivity.this, - BuySendSwapActivity.ActivityType.BUY, mAssetSymbol); + BuySendSwapActivity.ActivityType.BUY, mAssetSymbol, mChainId); } }); Button btnSend = findViewById(R.id.btn_send); btnSend.setOnClickListener(v -> Utils.openBuySendSwapActivity(AssetDetailActivity.this, - BuySendSwapActivity.ActivityType.SEND, mAssetSymbol)); + BuySendSwapActivity.ActivityType.SEND, mAssetSymbol, mChainId)); mBtnSwap = findViewById(R.id.btn_swap); @@ -234,7 +237,7 @@ public void onClick(View v) { } mBtnSwap.setOnClickListener(v -> Utils.openBuySendSwapActivity(AssetDetailActivity.this, - BuySendSwapActivity.ActivityType.SWAP, mAssetSymbol)); + BuySendSwapActivity.ActivityType.SWAP, mAssetSymbol, mChainId)); adjustButtonsVisibilities(); @@ -260,9 +263,6 @@ public void onClick(View v) { public boolean onTouch(View v, MotionEvent event) { v.getParent().requestDisallowInterceptTouchEvent(true); SmoothLineChartEquallySpaced chartES = (SmoothLineChartEquallySpaced) v; - if (chartES == null) { - return true; - } if (event.getAction() == MotionEvent.ACTION_MOVE || event.getAction() == MotionEvent.ACTION_DOWN) { chartES.drawLine(event.getRawX(), assetPrice); @@ -314,104 +314,93 @@ private void setUpAccountList() { new WalletCoinAdapter(WalletCoinAdapter.AdapterType.ACCOUNTS_LIST); mWalletTxCoinAdapter = new WalletCoinAdapter(WalletCoinAdapter.AdapterType.VISIBLE_ASSETS_LIST); - KeyringService keyringService = getKeyringService(); - JsonRpcService jsonRpcService = getJsonRpcService(); - if (keyringService != null && jsonRpcService != null && mWalletModel != null) { - keyringService.getKeyringInfo( + if (mWalletModel != null) { + mWalletModel.getKeyringModel().getKeyringPerId( AssetUtils.getKeyringForCoinType(mCoinType), keyringInfo -> { if (keyringInfo == null) return; accountInfos = keyringInfo.accountInfos; - jsonRpcService.getNetwork(mCoinType, selectedNetwork -> { - WalletListItemModel thisAssetItemModel = - new WalletListItemModel(R.drawable.ic_eth, mAsset.name, - mAsset.symbol, mAsset.tokenId, "", ""); - LiveDataUtil.observeOnce( - mWalletModel.getCryptoModel().getNetworkModel().mCryptoNetworks, - allNetworks -> { - Utils.getTxExtraInfo(new WeakReference<>(this), allNetworks, - selectedNetwork, accountInfos, - new BlockchainToken[] {mAsset}, false, - (assetPrices, fullTokenList, nativeAssetsBalances, - blockchainTokensBalances) -> { - thisAssetItemModel.setBlockchainToken(mAsset); - Utils.setUpTransactionList(this, accountInfos, - thisAssetItemModel, assetPrices, - fullTokenList, nativeAssetsBalances, - blockchainTokensBalances, - findViewById(R.id.rv_transactions), - this, mWalletTxCoinAdapter); - - double thisPrice = - Utils.getOrDefault(assetPrices, - mAsset.symbol.toLowerCase( - Locale.getDefault()), + WalletListItemModel thisAssetItemModel = + new WalletListItemModel(R.drawable.ic_eth, mAsset.name, + mAsset.symbol, mAsset.tokenId, "", ""); + LiveDataUtil.observeOnce( + mWalletModel.getCryptoModel().getNetworkModel().mCryptoNetworks, + allNetworks -> { + Utils.getTxExtraInfo(new WeakReference<>(this), allNetworks, + mAssetNetwork, accountInfos, + new BlockchainToken[] {mAsset}, false, + (assetPrices, fullTokenList, nativeAssetsBalances, + blockchainTokensBalances) -> { + thisAssetItemModel.setBlockchainToken(mAsset); + Utils.setUpTransactionList(this, accountInfos, + thisAssetItemModel, assetPrices, + fullTokenList, nativeAssetsBalances, + blockchainTokensBalances, + findViewById(R.id.rv_transactions), this, + mWalletTxCoinAdapter); + + double thisPrice = Utils.getOrDefault(assetPrices, + mAsset.symbol.toLowerCase( + Locale.getDefault()), + 0.0d); + List walletListItemModelList = + new ArrayList<>(); + for (AccountInfo accountInfo : accountInfos) { + final String accountAddressLower = + accountInfo.address.toLowerCase( + Locale.getDefault()); + double thisAccountBalance = + Utils.isNativeToken( + mAssetNetwork, mAsset) + ? Utils.getOrDefault( + nativeAssetsBalances, + accountAddressLower, 0.0d) + : Utils.getOrDefault( + Utils.getOrDefault( + blockchainTokensBalances, + accountAddressLower, + new HashMap()), + Utils.tokenToString(mAsset), 0.0d); - List - walletListItemModelList = - new ArrayList<>(); - for (AccountInfo accountInfo : accountInfos) { - final String accountAddressLower = - accountInfo.address.toLowerCase( - Locale.getDefault()); - double thisAccountBalance = - Utils.isNativeToken( - selectedNetwork, mAsset) - ? Utils.getOrDefault( - nativeAssetsBalances, - accountAddressLower, 0.0d) - : Utils.getOrDefault( - Utils.getOrDefault( - blockchainTokensBalances, - accountAddressLower, - new HashMap()), - Utils.tokenToString(mAsset), - 0.0d); - final String fiatBalanceString = - String.format(Locale.getDefault(), - "$%,.2f", - thisPrice - * thisAccountBalance); - final String cryptoBalanceString = - String.format(Locale.getDefault(), - "%.4f %s", - thisAccountBalance, - mAsset.symbol); - - // if NFT, only show the account that owns - // it (i.e. balance = 1) - if (mAsset.isNft - && thisAccountBalance != 1.) - continue; - - WalletListItemModel model = - new WalletListItemModel( - R.drawable.ic_eth, - accountInfo.name, - accountInfo.address, - fiatBalanceString, - cryptoBalanceString, - accountInfo.isImported); - model.setAccountInfo(accountInfo); - walletListItemModelList.add(model); - } - - if (walletCoinAdapter != null) { - walletCoinAdapter - .setWalletListItemModelList( - walletListItemModelList); - walletCoinAdapter.setOnWalletListItemClick( - AssetDetailActivity.this); - walletCoinAdapter.setWalletListItemType( - Utils.ACCOUNT_ITEM); - rvAccounts.setAdapter(walletCoinAdapter); - rvAccounts.setLayoutManager( - new LinearLayoutManager( - AssetDetailActivity.this)); - } - }); - }); - }); + final String fiatBalanceString = String.format( + Locale.getDefault(), "$%,.2f", + thisPrice * thisAccountBalance); + final String cryptoBalanceString = + String.format(Locale.getDefault(), + "%.4f %s", thisAccountBalance, + mAsset.symbol); + + // If NFT, only show the account that owns + // it (i.e. balance = 1) + if (mAsset.isNft && thisAccountBalance != 1.) + continue; + + WalletListItemModel model = + new WalletListItemModel( + R.drawable.ic_eth, + accountInfo.name, + accountInfo.address, + fiatBalanceString, + cryptoBalanceString, + accountInfo.isImported); + model.setAccountInfo(accountInfo); + walletListItemModelList.add(model); + } + + if (walletCoinAdapter != null) { + walletCoinAdapter.setWalletListItemModelList( + walletListItemModelList); + walletCoinAdapter.setOnWalletListItemClick( + AssetDetailActivity.this); + walletCoinAdapter.setWalletListItemType( + Utils.ACCOUNT_ITEM); + rvAccounts.setAdapter(walletCoinAdapter); + rvAccounts.setLayoutManager( + new LinearLayoutManager( + AssetDetailActivity.this)); + } + }); + }); }); } } @@ -423,18 +412,16 @@ private void getBlockchainToken(Runnable callback) { return; } - getJsonRpcService().getNetwork(mCoinType, selectedNetwork -> { - TokenUtils.getExactUserAsset(getBraveWalletService(), selectedNetwork, - selectedNetwork.coin, mAssetSymbol, mAssetName, mAssetId, mContractAddress, - mAssetDecimals, new Callback() { - @Override - public void onResult(BlockchainToken token) { - assert token != null; - mAsset = token; - callback.run(); - } - }); - }); + TokenUtils.getExactUserAsset(getBraveWalletService(), mAssetNetwork, mAssetNetwork.coin, + mAssetSymbol, mAssetName, mAssetId, mContractAddress, mAssetDecimals, + new Callback() { + @Override + public void onResult(BlockchainToken token) { + assert token != null; + mAsset = token; + callback.run(); + } + }); } @Override @@ -507,19 +494,15 @@ private void showHideBuyUi() { AndroidUtils.gone(mBtnBuy); return; } - if (mWalletModel == null) return; - - LiveDataUtil.observeOnce(mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork, - selectedNetwork -> { - mWalletModel.getCryptoModel().getBuyModel().isBuySupported(selectedNetwork, - mAssetSymbol, mContractAddress, mChainId, - BuyModel.SUPPORTED_RAMP_PROVIDERS, isBuyEnabled -> { - if (isBuyEnabled) { - AndroidUtils.show(mBtnBuy); - } else { - AndroidUtils.gone(mBtnBuy); - } - }); + if (JavaUtils.anyNull(mWalletModel, mAssetNetwork)) return; + + mWalletModel.getCryptoModel().getBuyModel().isBuySupported(mAssetNetwork, mAssetSymbol, + mContractAddress, mChainId, BuyModel.SUPPORTED_RAMP_PROVIDERS, isBuyEnabled -> { + if (isBuyEnabled) { + AndroidUtils.show(mBtnBuy); + } else { + AndroidUtils.gone(mBtnBuy); + } }); } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletBaseActivity.java b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletBaseActivity.java index 14685ae5b42c..ed9a6375ebc2 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletBaseActivity.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletBaseActivity.java @@ -58,7 +58,6 @@ public void onUserInteraction() { public void onConnectionError(MojoException e) { if (mKeyringServiceObserver != null) { mKeyringServiceObserver.close(); - mKeyringServiceObserver.destroy(); mKeyringServiceObserver = null; } if (mTxServiceObserver != null) { @@ -236,7 +235,6 @@ public void finishNativeInitialization() { public void onDestroy() { if (mKeyringServiceObserver != null) { mKeyringServiceObserver.close(); - mKeyringServiceObserver.destroy(); } if (mTxServiceObserver != null) { mTxServiceObserver.close(); diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BuySendSwapActivity.java b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BuySendSwapActivity.java index 7fd2a4f4effa..9da1857e49d9 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BuySendSwapActivity.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BuySendSwapActivity.java @@ -53,7 +53,6 @@ import org.chromium.base.Log; import org.chromium.brave_wallet.mojom.AccountInfo; import org.chromium.brave_wallet.mojom.BlockchainToken; -import org.chromium.brave_wallet.mojom.BraveWalletConstants; import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.GasEstimation1559; import org.chromium.brave_wallet.mojom.NetworkInfo; @@ -115,6 +114,9 @@ public class BuySendSwapActivity extends BraveWalletBaseActivity implements AdapterView.OnItemSelectedListener, BarcodeTracker.BarcodeGraphicTrackerCallback, ApprovedTxObserver { + public static final String ACTIVITY_TYPE = "activityType"; + public static final String ASSET_SYMBOL = "swapFromAssetSymbol"; + public static final String ASSET_CHAIN_ID = "ASSET_CHAIN"; private static final String TAG = "BuySendSwap"; private static final int RC_HANDLE_CAMERA_PERM = 113; // Intent request code to handle updating play services if needed. @@ -128,35 +130,9 @@ public class BuySendSwapActivity extends BraveWalletBaseActivity private List mAllAccountInfos; private List mAccountInfos; private SendModel mSendModel; - private NetworkInfo[] mNetworks; - - public enum ActivityType { - BUY(0), - SEND(1), - SWAP(2); - - private int value; - private static Map map = new HashMap<>(); - - private ActivityType(int value) { - this.value = value; - } - - static { - for (ActivityType activityType : ActivityType.values()) { - map.put(activityType.value, activityType); - } - } - - public static ActivityType valueOf(int activityType) { - return (ActivityType) map.get(activityType); - } - - public int getValue() { - return value; - } - } - + private List mNetworks; + private String mIntentChainId; + private boolean mCanUpdateAssetSymbol; public String mActivateAllowanceTxId; private ActivityType mActivityType; private AccountSpinnerAdapter mCustomAccountAdapter; @@ -215,8 +191,9 @@ protected void triggerLayoutInflation() { mActivateAllowanceTxId = ""; Intent intent = getIntent(); mActivityType = ActivityType.valueOf( - intent.getIntExtra("activityType", ActivityType.BUY.getValue())); - mToAssetSymbol = getIntent().getStringExtra("swapFromAssetSymbol"); + intent.getIntExtra(ACTIVITY_TYPE, ActivityType.BUY.getValue())); + mToAssetSymbol = getIntent().getStringExtra(ASSET_SYMBOL); + mExecutor = Executors.newSingleThreadExecutor(); mHandler = new Handler(Looper.getMainLooper()); @@ -250,12 +227,14 @@ protected void triggerLayoutInflation() { BraveActivity activity = BraveActivity.getBraveActivity(); mWalletModel = activity.getWalletModel(); mSendModel = mWalletModel.getCryptoModel().createSendModel(); + mIntentChainId = intent.getStringExtra(ASSET_CHAIN_ID); + updateNetworkPerAssetChain(mIntentChainId); } catch (ActivityNotFoundException e) { Log.e(TAG, "triggerLayoutInflation " + e); } mNetworkSpinner = findViewById(R.id.network_spinner); - mNetworkAdapter = new NetworkSpinnerAdapter(this, new NetworkInfo[0]); + mNetworkAdapter = new NetworkSpinnerAdapter(this, Collections.emptyList()); mNetworkSpinner.setAdapter(mNetworkAdapter); mNetworkSpinner.setOnItemSelectedListener(this); mCustomAccountAdapter = @@ -741,9 +720,11 @@ private void populateBalance(String balance, boolean from, int responseDecimals) getSendSwapQuota(true, false); } } - private void setSelectedNetwork(NetworkInfo networkInfo, NetworkInfo[] networkInfos) { + private void setSelectedNetwork(NetworkInfo networkInfo, List networkInfos) { if (isSameSelectedNetwork(networkInfo)) return; - mNetworkSpinner.setSelection(getIndexOf(networkInfo, networkInfos)); + mNetworkSpinner.setOnItemSelectedListener(null); + mNetworkSpinner.setSelection(getIndexOf(networkInfo, networkInfos), false); + mNetworkSpinner.setOnItemSelectedListener(this); } private boolean isSameSelectedNetwork(NetworkInfo networkInfo) { @@ -754,9 +735,9 @@ private boolean isSameSelectedNetwork(NetworkInfo networkInfo) { return false; } - private int getIndexOf(NetworkInfo selectedNetwork, NetworkInfo[] networksInfos) { - for (int i = 0; i < networksInfos.length; i++) { - NetworkInfo networkInfo = networksInfos[i]; + private int getIndexOf(NetworkInfo selectedNetwork, List networksInfos) { + for (int i = 0; i < networksInfos.size(); i++) { + NetworkInfo networkInfo = networksInfos.get(i); if (networkInfo.chainId.equals(selectedNetwork.chainId) && networkInfo.coin == selectedNetwork.coin) { return i; @@ -1362,6 +1343,33 @@ private TextWatcher getTextWatcherFromToValueText(boolean from) { return new FilterTextFromToValueText(from); } + public enum ActivityType { + BUY(0), + SEND(1), + SWAP(2); + + private int value; + private static Map map = new HashMap<>(); + + private ActivityType(int value) { + this.value = value; + } + + static { + for (ActivityType activityType : ActivityType.values()) { + map.put(activityType.value, activityType); + } + } + + public static ActivityType valueOf(int activityType) { + return (ActivityType) map.get(activityType); + } + + public int getValue() { + return value; + } + } + private class FilterTextFromToValueText implements TextWatcher { boolean mFrom; @@ -1695,8 +1703,9 @@ private void initAccountsUI() { mCustomAccountAdapter.setAccounts(mAccountInfos); if (mAccountInfos.size() > 0) { + mAccountSpinner.setOnItemSelectedListener(null); mAccountSpinner.setSelection( - WalletUtils.getSelectedAccountIndex(mSelectedAccount, mAccountInfos)); + WalletUtils.getSelectedAccountIndex(mSelectedAccount, mAccountInfos), false); } mAccountSpinner.setOnItemSelectedListener(this); // Before updating, make sure we are on a network with the same coin type @@ -1710,21 +1719,29 @@ public void finishNativeInitialization() { super.finishNativeInitialization(); InitSwapService(); assert mWalletModel != null; - mWalletModel.getCryptoModel().getNetworkModel().mChainNetworkAllNetwork.observe( - this, chainAllNetworksAllNetwork -> { - if (mCurrentChainId != null - && !mCurrentChainId.equals(chainAllNetworksAllNetwork.first)) { - mToAssetSymbol = chainAllNetworksAllNetwork.second.symbol; + mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork.observe( + this, networkInfo -> { + // Only use network symbol after network switch + if (mIntentChainId == null || mCanUpdateAssetSymbol) { + mToAssetSymbol = networkInfo.symbol; + } + if (mIntentChainId != null && mIntentChainId.equals(networkInfo.chainId)) { + mCanUpdateAssetSymbol = true; } - mCurrentChainId = chainAllNetworksAllNetwork.first; - mSelectedNetwork = chainAllNetworksAllNetwork.second; + }); + mWalletModel.getCryptoModel().getNetworkModel().mChainNetworkAllNetwork.observe( + this, chainNetworkAllNetwork -> { + if (JavaUtils.anyNull( + chainNetworkAllNetwork.first, chainNetworkAllNetwork.second)) + return; + mCurrentChainId = chainNetworkAllNetwork.first; + mSelectedNetwork = chainNetworkAllNetwork.second; mNetworks = mActivityType != ActivityType.SEND ? mWalletModel.getCryptoModel() .getNetworkModel() .stripNoBuySwapNetworks( - chainAllNetworksAllNetwork.third, mActivityType) - : chainAllNetworksAllNetwork.third; - + chainNetworkAllNetwork.third, mActivityType) + : chainNetworkAllNetwork.third; mNetworkAdapter.setNetworks(mNetworks); setSelectedNetwork(mSelectedNetwork, mNetworks); @@ -1811,4 +1828,10 @@ public void onTransactionStatusChanged(TransactionInfo txInfo) { showSwapButtonText(); } } + + private void updateNetworkPerAssetChain(String assetChainId) { + if (TextUtils.isEmpty(assetChainId)) return; + mWalletModel.getCryptoModel().getNetworkModel().setNetworkWithAccountCheck( + assetChainId, hasSetNetwork -> {}); + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/NetworkSelectorActivity.java b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/NetworkSelectorActivity.java index 50016a2aedaf..649b3103bac8 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/NetworkSelectorActivity.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/NetworkSelectorActivity.java @@ -38,6 +38,7 @@ public class NetworkSelectorActivity extends BraveWalletBaseActivity implements NetworkSelectorAdapter.NetworkClickListener { public static String NETWORK_SELECTOR_MODE = "network_selector_mode"; + public static String NETWORK_SELECTOR_KEY = "network_selector_key"; private static final String TAG = "NetworkSelector"; private NetworkSelectorModel.Mode mMode; private RecyclerView mRVNetworkSelector; @@ -45,6 +46,7 @@ public class NetworkSelectorActivity private MaterialToolbar mToolbar; private String mSelectedNetwork; private SettingsLauncher mSettingsLauncher; + private String mKey; private WalletModel mWalletModel; private NetworkSelectorModel mNetworkSelectorModel; @@ -62,6 +64,9 @@ protected void triggerLayoutInflation() { mMode = JavaUtils.safeVal( (NetworkSelectorModel.Mode) intent.getSerializableExtra(NETWORK_SELECTOR_MODE), NetworkSelectorModel.Mode.DEFAULT_WALLET_NETWORK); + // Key acts as a binding contract between the caller and network selection activity to share + // local network selection actions in LOCAL_NETWORK_FILTER mode + mKey = intent.getStringExtra(NETWORK_SELECTOR_KEY); mRVNetworkSelector = findViewById(R.id.rv_network_activity); onInitialLayoutInflationComplete(); } @@ -82,7 +87,8 @@ private void initState() { mSettingsLauncher = new BraveSettingsLauncherImpl(); mNetworkSelectorModel = - mWalletModel.getCryptoModel().getNetworkModel().openNetworkSelectorModel(mMode); + mWalletModel.getCryptoModel().getNetworkModel().openNetworkSelectorModel( + mKey, mMode, null); networkSelectorAdapter = new NetworkSelectorAdapter(this, new ArrayList<>()); mRVNetworkSelector.setAdapter(networkSelectorAdapter); networkSelectorAdapter.setOnNetworkItemSelected(this); @@ -110,14 +116,13 @@ private void setSelectedNetworkObserver() { mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork, networkInfo -> { updateNetworkSelection(networkInfo); }); } else if (mMode == NetworkSelectorModel.Mode.LOCAL_NETWORK_FILTER) { - mNetworkSelectorModel.mSelectedNetwork.observe( + mNetworkSelectorModel.getSelectedNetwork().observe( this, networkInfo -> { updateNetworkSelection(networkInfo); }); } } private void updateNetworkSelection(NetworkInfo networkInfo) { if (networkInfo != null) { - mSelectedNetwork = Utils.getShortNameOfNetwork(networkInfo.chainName); networkSelectorAdapter.setSelectedNetwork(networkInfo.chainName); } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/NetworkSpinnerAdapter.java b/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/NetworkSpinnerAdapter.java index 2549bebff246..eab1f60a4ea6 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/NetworkSpinnerAdapter.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/NetworkSpinnerAdapter.java @@ -21,18 +21,18 @@ import org.chromium.chrome.R; import org.chromium.chrome.browser.crypto_wallet.util.Utils; -import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class NetworkSpinnerAdapter extends BaseAdapter implements SpinnerAdapter { private Context context; private LayoutInflater inflater; - private NetworkInfo[] mNetworkInfos; + private List mNetworkInfos; private ExecutorService mExecutor; private Handler mHandler; - public NetworkSpinnerAdapter(Context applicationContext, NetworkInfo[] networkInfos) { + public NetworkSpinnerAdapter(Context applicationContext, List networkInfos) { this.context = applicationContext; inflater = (LayoutInflater.from(applicationContext)); mNetworkInfos = networkInfos; @@ -41,17 +41,17 @@ public NetworkSpinnerAdapter(Context applicationContext, NetworkInfo[] networkIn } public NetworkInfo getNetwork(int position) { - return mNetworkInfos[position]; + return mNetworkInfos.get(position); } @Override public int getCount() { - return mNetworkInfos.length; + return mNetworkInfos.size(); } @Override public Object getItem(int position) { - return mNetworkInfos[position]; + return mNetworkInfos.get(position); } @Override @@ -68,7 +68,7 @@ public long getItemId(int position) { public View getView(int position, View view, ViewGroup viewGroup) { view = inflater.inflate(R.layout.network_spinner_items, null); TextView name = view.findViewById(R.id.network_name_text); - name.setText(Utils.getShortNameOfNetwork(mNetworkInfos[position].chainName)); + name.setText(Utils.getShortNameOfNetwork(mNetworkInfos.get(position).chainName)); ImageView networkPicture = view.findViewById(R.id.network_picture); networkPicture.setVisibility(View.GONE); name.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null); @@ -79,15 +79,15 @@ public View getView(int position, View view, ViewGroup viewGroup) { public View getDropDownView(int position, View view, ViewGroup viewGroup) { view = inflater.inflate(R.layout.network_spinner_items, null); TextView name = (TextView) view.findViewById(R.id.network_name_text); - name.setText(mNetworkInfos[position].chainName); + name.setText(mNetworkInfos.get(position).chainName); ImageView networkPicture = view.findViewById(R.id.network_picture); Utils.setBlockiesBitmapResource( - mExecutor, mHandler, networkPicture, mNetworkInfos[position].chainName, false); + mExecutor, mHandler, networkPicture, mNetworkInfos.get(position).chainName, false); return view; } - public void setNetworks(NetworkInfo[] networkInfos) { + public void setNetworks(List networkInfos) { mNetworkInfos = networkInfos; notifyDataSetChanged(); } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/WalletCoinAdapter.java b/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/WalletCoinAdapter.java index d7f65d7b8047..5bc62d203ddd 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/WalletCoinAdapter.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/WalletCoinAdapter.java @@ -29,6 +29,7 @@ import org.chromium.chrome.browser.app.domain.PortfolioModel; import org.chromium.chrome.browser.crypto_wallet.listeners.OnWalletListItemClick; import org.chromium.chrome.browser.crypto_wallet.model.WalletListItemModel; +import org.chromium.chrome.browser.crypto_wallet.util.AndroidUtils; import org.chromium.chrome.browser.crypto_wallet.util.ImageLoader; import org.chromium.chrome.browser.crypto_wallet.util.Utils; @@ -147,9 +148,18 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) { } } - if (mType == AdapterType.EDIT_VISIBLE_ASSETS_LIST || mType == AdapterType.BUY_ASSETS_LIST - || mType == AdapterType.SEND_ASSETS_LIST || mType == AdapterType.SWAP_TO_ASSETS_LIST - || mType == AdapterType.SWAP_FROM_ASSETS_LIST) { + if (isAssetSelectionType() || mType == AdapterType.VISIBLE_ASSETS_LIST) { + if (walletListItemModel.isNativeAsset()) { + AndroidUtils.gone(holder.ivNetworkImage); + } else { + AndroidUtils.show(holder.ivNetworkImage); + Utils.setBitmapResource(mExecutor, mHandler, context, + walletListItemModel.getNetworkIcon(), walletListItemModel.getIcon(), + holder.ivNetworkImage, null, true); + } + } + + if (isAssetSelectionType()) { holder.text1Text.setVisibility(View.GONE); holder.text2Text.setVisibility(View.GONE); if (mType == AdapterType.EDIT_VISIBLE_ASSETS_LIST) { @@ -231,6 +241,12 @@ public void onCheckedChanged( } } + private boolean isAssetSelectionType() { + return mType == AdapterType.EDIT_VISIBLE_ASSETS_LIST || mType == AdapterType.BUY_ASSETS_LIST + || mType == AdapterType.SEND_ASSETS_LIST || mType == AdapterType.SWAP_TO_ASSETS_LIST + || mType == AdapterType.SWAP_FROM_ASSETS_LIST; + } + @Override public int getItemCount() { return walletListItemModelList.size(); @@ -241,10 +257,8 @@ public void setWalletCoinAdapterType(AdapterType type) { } public void setWalletListItemModelList(List walletListItemModelList) { - this.walletListItemModelList = removeDuplicates(walletListItemModelList); - if (mType == AdapterType.EDIT_VISIBLE_ASSETS_LIST || mType == AdapterType.BUY_ASSETS_LIST - || mType == AdapterType.SEND_ASSETS_LIST || mType == AdapterType.SWAP_TO_ASSETS_LIST - || mType == AdapterType.SWAP_FROM_ASSETS_LIST) { + this.walletListItemModelList = walletListItemModelList; + if (isAssetSelectionType()) { walletListItemModelListCopy.addAll(this.walletListItemModelList); mCheckedPositions.clear(); } @@ -305,43 +319,6 @@ public void updateSelectedNetwork(String title, String subTitle) { } } - // Removing duplicates will allow the recycler viewer to render a clean list without showing the - // same assets multiple times. Currently, the list of available assets is fetched from Core - // API the returns a merged list containing the available assets per ramp provider. - // It's not unusual to have the same asset multiple times with the same contract address all - // upper case from a ramp provider and all lower case from another one. Thus it's important to - // compare the contract addresses ignoring case. - private List removeDuplicates( - List walletListItemModelList) { - List result = new ArrayList<>(); - for (WalletListItemModel item : walletListItemModelList) { - if (item.getBlockchainToken() == null) { - // If blockchain token is null the item can be safely added without any risk - // of duplication. - result.add(item); - continue; - } - String contractAddress = item.getBlockchainToken().contractAddress; - boolean duplicate = false; - for (WalletListItemModel itemResult : result) { - // IMPORTANT: use `equalsIgnoreCase` to detect two contract addresses with different - // capitalization. - if (contractAddress.equalsIgnoreCase( - itemResult.getBlockchainToken().contractAddress)) { - // Duplicated item detected! - duplicate = true; - break; - } - } - // Do not add duplicated item. - if (!duplicate) { - result.add(item); - } - } - - return result; - } - private void updateSelectedNetwork(int selectedAccountPosition) { walletListItemModelList.get(previousSelectedPos).setIsUserSelected(false); notifyItemChanged(previousSelectedPos); @@ -352,30 +329,31 @@ private void updateSelectedNetwork(int selectedAccountPosition) { } public static class ViewHolder extends RecyclerView.ViewHolder { - public ImageView iconImg; - public TextView titleText; - public TextView subTitleText; - public TextView txStatus; - public TextView text1Text; - public TextView text2Text; - public CheckBox assetCheck; - public LinearLayout feeLayout; - public TextView feeText; - public ImageView iconTrash; - public ImageView ivSelected; + private final ImageView iconImg; + private final TextView titleText; + private final TextView subTitleText; + private final TextView txStatus; + private final TextView text1Text; + private final TextView text2Text; + private final CheckBox assetCheck; + private final TextView feeText; + private final ImageView iconTrash; + private final ImageView ivSelected; + private final ImageView ivNetworkImage; public ViewHolder(View itemView) { super(itemView); - this.iconImg = itemView.findViewById(R.id.icon); - this.titleText = itemView.findViewById(R.id.title); - this.txStatus = itemView.findViewById(R.id.status); - this.subTitleText = itemView.findViewById(R.id.sub_title); - this.text1Text = itemView.findViewById(R.id.text1); - this.text2Text = itemView.findViewById(R.id.text2); - this.assetCheck = itemView.findViewById(R.id.assetCheck); - this.feeText = itemView.findViewById(R.id.fee_text); - this.iconTrash = itemView.findViewById(R.id.icon_trash); - this.ivSelected = itemView.findViewById(R.id.iv_selected); + iconImg = itemView.findViewById(R.id.icon); + titleText = itemView.findViewById(R.id.title); + txStatus = itemView.findViewById(R.id.status); + subTitleText = itemView.findViewById(R.id.sub_title); + text1Text = itemView.findViewById(R.id.text1); + text2Text = itemView.findViewById(R.id.text2); + assetCheck = itemView.findViewById(R.id.assetCheck); + feeText = itemView.findViewById(R.id.fee_text); + iconTrash = itemView.findViewById(R.id.icon_trash); + ivSelected = itemView.findViewById(R.id.iv_selected); + ivNetworkImage = itemView.findViewById(R.id.iv_network_Icon); } public void resetObservers() { diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/EditVisibleAssetsBottomSheetDialogFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/EditVisibleAssetsBottomSheetDialogFragment.java index ea165694e9cf..2d3d5fffd09a 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/EditVisibleAssetsBottomSheetDialogFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/EditVisibleAssetsBottomSheetDialogFragment.java @@ -27,6 +27,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; @@ -59,10 +60,14 @@ import org.chromium.chrome.browser.crypto_wallet.model.WalletListItemModel; import org.chromium.chrome.browser.crypto_wallet.observers.KeyringServiceObserverImpl; import org.chromium.chrome.browser.crypto_wallet.observers.KeyringServiceObserverImpl.KeyringServiceObserverImplDelegate; +import org.chromium.chrome.browser.crypto_wallet.util.AndroidUtils; +import org.chromium.chrome.browser.crypto_wallet.util.NetworkUtils; import org.chromium.chrome.browser.crypto_wallet.util.TokenUtils; import org.chromium.chrome.browser.crypto_wallet.util.Utils; +import org.chromium.chrome.browser.util.LiveDataUtil; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -79,6 +84,7 @@ public class EditVisibleAssetsBottomSheetDialogFragment extends BottomSheetDialo private WalletModel mWalletModel; private KeyringServiceObserverImpl mKeyringServiceObserver; private OnEditVisibleItemClickListener mOnEditVisibleItemClickListener; + private List mCryptoNetworks; public interface DismissListener { void onDismiss(Boolean isAssetsListChanged); @@ -91,6 +97,7 @@ public static EditVisibleAssetsBottomSheetDialogFragment newInstance( private EditVisibleAssetsBottomSheetDialogFragment(WalletCoinAdapter.AdapterType type) { mType = type; + mCryptoNetworks = Collections.emptyList(); } // TODO (Wengling): add an interface for getting services that can be shared between activity @@ -203,7 +210,6 @@ public void onDismiss(DialogInterface dialog) { } if (mKeyringServiceObserver != null) { mKeyringServiceObserver.close(); - mKeyringServiceObserver.destroy(); mKeyringServiceObserver = null; } } @@ -264,31 +270,52 @@ public void onClick(View clickView) { if (blockchainRegistry != null) { BraveWalletService braveWalletService = getBraveWalletService(); assert braveWalletService != null; - if (mType == WalletCoinAdapter.AdapterType.EDIT_VISIBLE_ASSETS_LIST) { - assert mSelectedNetwork != null; - TokenUtils.getUserAssetsFiltered(braveWalletService, mSelectedNetwork, - mSelectedNetwork.coin, TokenUtils.TokenType.ALL, userAssets -> { + LiveDataUtil.observeOnce( + mWalletModel.getCryptoModel().getNetworkModel().mCryptoNetworks, + networkInfos -> { + mCryptoNetworks = networkInfos; + if (mType == WalletCoinAdapter.AdapterType.EDIT_VISIBLE_ASSETS_LIST) { + assert mSelectedNetwork != null; + TextView networkName = view.findViewById(R.id.edit_visible_tv_network); + AndroidUtils.show(networkName); + networkName.setText( + Utils.getShortNameOfNetwork(mSelectedNetwork.chainName)); + networkName.setOnClickListener(v -> { + Toast.makeText(requireContext(), mSelectedNetwork.chainName, + Toast.LENGTH_SHORT) + .show(); + }); + TokenUtils.getUserAssetsFiltered(braveWalletService, mSelectedNetwork, + mSelectedNetwork.coin, TokenUtils.TokenType.ALL, userAssets -> { + TokenUtils.getAllTokensFiltered(braveWalletService, + blockchainRegistry, mSelectedNetwork, + mSelectedNetwork.coin, TokenUtils.TokenType.ALL, + tokens -> { + setUpAssetsList(view, tokens, userAssets); + }); + }); + } else if (mType == WalletCoinAdapter.AdapterType.SEND_ASSETS_LIST) { + assert mSelectedNetwork != null; + TokenUtils.getUserAssetsFiltered(braveWalletService, mSelectedNetwork, + mSelectedNetwork.coin, TokenUtils.TokenType.ALL, tokens -> { + setUpAssetsList(view, tokens, new BlockchainToken[0]); + }); + } else if (mType == WalletCoinAdapter.AdapterType.SWAP_TO_ASSETS_LIST + || mType == WalletCoinAdapter.AdapterType.SWAP_FROM_ASSETS_LIST) { + assert mSelectedNetwork != null; TokenUtils.getAllTokensFiltered(braveWalletService, blockchainRegistry, mSelectedNetwork, mSelectedNetwork.coin, - TokenUtils.TokenType.ALL, - tokens -> { setUpAssetsList(view, tokens, userAssets); }); - }); - } else if (mType == WalletCoinAdapter.AdapterType.SEND_ASSETS_LIST) { - assert mSelectedNetwork != null; - TokenUtils.getUserAssetsFiltered(braveWalletService, mSelectedNetwork, - mSelectedNetwork.coin, TokenUtils.TokenType.ALL, - tokens -> { setUpAssetsList(view, tokens, new BlockchainToken[0]); }); - } else if (mType == WalletCoinAdapter.AdapterType.SWAP_TO_ASSETS_LIST - || mType == WalletCoinAdapter.AdapterType.SWAP_FROM_ASSETS_LIST) { - assert mSelectedNetwork != null; - TokenUtils.getAllTokensFiltered(braveWalletService, blockchainRegistry, - mSelectedNetwork, mSelectedNetwork.coin, TokenUtils.TokenType.ERC20, - tokens -> { setUpAssetsList(view, tokens, new BlockchainToken[0]); }); - } else if (mType == WalletCoinAdapter.AdapterType.BUY_ASSETS_LIST) { - TokenUtils.getBuyTokensFiltered(blockchainRegistry, mSelectedNetwork, - TokenUtils.TokenType.ALL, BuyModel.SUPPORTED_RAMP_PROVIDERS, - tokens -> { setUpAssetsList(view, tokens, new BlockchainToken[0]); }); - } + TokenUtils.TokenType.ERC20, tokens -> { + setUpAssetsList(view, tokens, new BlockchainToken[0]); + }); + } else if (mType == WalletCoinAdapter.AdapterType.BUY_ASSETS_LIST) { + TokenUtils.getBuyTokensFiltered(blockchainRegistry, mSelectedNetwork, + TokenUtils.TokenType.ALL, BuyModel.SUPPORTED_RAMP_PROVIDERS, + tokens -> { + setUpAssetsList(view, tokens, new BlockchainToken[0]); + }); + } + }); } KeyringService keyringService = getKeyringService(); assert keyringService != null; @@ -497,13 +524,17 @@ private void setUpAssetsList( List walletListItemModelList = new ArrayList<>(); String tokensPath = BlockchainRegistryFactory.getInstance().getTokensIconsLocation(); for (int i = 0; i < tokens.length; i++) { - WalletListItemModel itemModel = - new WalletListItemModel(Utils.getCoinIcon(tokens[i].coin), tokens[i].name, - tokens[i].symbol, tokens[i].tokenId, "", ""); - itemModel.setBlockchainToken(tokens[i]); - itemModel.setIconPath("file://" + tokensPath + "/" + tokens[i].logo); + BlockchainToken token = tokens[i]; + WalletListItemModel itemModel = new WalletListItemModel( + Utils.getCoinIcon(token.coin), token.name, token.symbol, token.tokenId, "", ""); + NetworkInfo assetNetwork = NetworkUtils.findNetwork(mCryptoNetworks, token.chainId); + itemModel.setBrowserResourcePath(tokensPath); + itemModel.setAssetNetwork(assetNetwork); + + itemModel.setBlockchainToken(token); + itemModel.setIconPath("file://" + tokensPath + "/" + token.logo); - boolean isUserSelected = selectedTokensSymbols.contains(Utils.tokenToString(tokens[i])); + boolean isUserSelected = selectedTokensSymbols.contains(Utils.tokenToString(token)); itemModel.setIsUserSelected(isUserSelected); walletListItemModelList.add(itemModel); } @@ -603,60 +634,59 @@ public void onAssetCheckedChanged( WalletListItemModel walletListItemModel, CheckBox assetCheck, boolean isChecked) { if (mType != WalletCoinAdapter.AdapterType.EDIT_VISIBLE_ASSETS_LIST) return; JsonRpcService jsonRpcService = getJsonRpcService(); - if (jsonRpcService == null) return; - + if (mWalletModel == null) return; BlockchainToken thisToken = walletListItemModel.getBlockchainToken(); - jsonRpcService.getNetwork(thisToken.coin, selectedNetwork -> { - TokenUtils.isCustomToken(getBlockchainRegistry(), selectedNetwork, selectedNetwork.coin, - thisToken, isCustom -> { - // Only show add asset dialog on click when: - // 1. It is an ERC721 token - // 2. It is a token listed in Registry - // 3. It is not user added (i.e. doesn't have a token id) - if (thisToken.isErc721 && !isCustom - && (thisToken.tokenId == null - || thisToken.tokenId.trim().isEmpty())) { - showAddAssetDialog(); - walletListItemModel.setIsUserSelected( - false); // The added token is different from the listed one - itemCheckboxConsistency(walletListItemModel, assetCheck, isChecked); + NetworkInfo selectedNetwork = + mWalletModel.getCryptoModel().getNetworkModel().getNetwork(thisToken.chainId); + + TokenUtils.isCustomToken(getBlockchainRegistry(), selectedNetwork, selectedNetwork.coin, + thisToken, isCustom -> { + // Only show add asset dialog on click when: + // 1. It is an ERC721 token + // 2. It is a token listed in Registry + // 3. It is not user added (i.e. doesn't have a token id) + if (thisToken.isErc721 && !isCustom + && (thisToken.tokenId == null || thisToken.tokenId.trim().isEmpty())) { + showAddAssetDialog(); + walletListItemModel.setIsUserSelected( + false); // The added token is different from the listed one + itemCheckboxConsistency(walletListItemModel, assetCheck, isChecked); + } else { + BraveWalletService braveWalletService = getBraveWalletService(); + // TODO: all the asserts need to be removed. Shall do proper + // error handling instead. + assert braveWalletService != null; + if (!isCustom) { + if (isChecked) { + braveWalletService.addUserAsset(thisToken, (success) -> { + if (success) { + walletListItemModel.setIsUserSelected(true); + } + itemCheckboxConsistency( + walletListItemModel, assetCheck, isChecked); + }); + } else { + braveWalletService.removeUserAsset(thisToken, (success) -> { + if (success) { + walletListItemModel.setIsUserSelected(false); + } + itemCheckboxConsistency( + walletListItemModel, assetCheck, isChecked); + }); + } } else { - BraveWalletService braveWalletService = getBraveWalletService(); - // TODO: all the asserts need to be removed. Shall do proper - // error handling instead. - assert braveWalletService != null; - if (!isCustom) { - if (isChecked) { - braveWalletService.addUserAsset(thisToken, (success) -> { - if (success) { - walletListItemModel.setIsUserSelected(true); - } - itemCheckboxConsistency( - walletListItemModel, assetCheck, isChecked); - }); - } else { - braveWalletService.removeUserAsset(thisToken, (success) -> { + braveWalletService.setUserAssetVisible( + thisToken, isChecked, success -> { if (success) { - walletListItemModel.setIsUserSelected(false); + walletListItemModel.setIsUserSelected(isChecked); } itemCheckboxConsistency( walletListItemModel, assetCheck, isChecked); }); - } - } else { - braveWalletService.setUserAssetVisible( - thisToken, isChecked, success -> { - if (success) { - walletListItemModel.setIsUserSelected(isChecked); - } - itemCheckboxConsistency( - walletListItemModel, assetCheck, isChecked); - }); - } - mIsAssetsListChanged = true; } - }); - }); + mIsAssetsListChanged = true; + } + }); } private void itemCheckboxConsistency( diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/PortfolioFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/PortfolioFragment.java index 7e8930616219..6b8dffbefbde 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/PortfolioFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/PortfolioFragment.java @@ -38,6 +38,7 @@ import org.chromium.brave_wallet.mojom.AccountInfo; import org.chromium.brave_wallet.mojom.AssetPriceTimeframe; import org.chromium.brave_wallet.mojom.BlockchainToken; +import org.chromium.brave_wallet.mojom.BraveWalletConstants; import org.chromium.brave_wallet.mojom.JsonRpcService; import org.chromium.brave_wallet.mojom.NetworkInfo; import org.chromium.brave_wallet.mojom.TransactionInfo; @@ -45,11 +46,13 @@ import org.chromium.brave_wallet.mojom.TransactionType; import org.chromium.chrome.R; import org.chromium.chrome.browser.app.BraveActivity; +import org.chromium.chrome.browser.app.domain.NetworkSelectorModel; import org.chromium.chrome.browser.app.domain.PortfolioModel; import org.chromium.chrome.browser.app.domain.WalletModel; import org.chromium.chrome.browser.app.helpers.Api33AndPlusBackPressHelper; import org.chromium.chrome.browser.crypto_wallet.BlockchainRegistryFactory; import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletActivity; +import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletBaseActivity; import org.chromium.chrome.browser.crypto_wallet.activities.NftDetailActivity; import org.chromium.chrome.browser.crypto_wallet.adapters.WalletCoinAdapter; import org.chromium.chrome.browser.crypto_wallet.listeners.OnWalletListItemClick; @@ -57,6 +60,7 @@ import org.chromium.chrome.browser.crypto_wallet.util.AndroidUtils; import org.chromium.chrome.browser.crypto_wallet.util.AssetUtils; import org.chromium.chrome.browser.crypto_wallet.util.JavaUtils; +import org.chromium.chrome.browser.crypto_wallet.util.NetworkUtils; import org.chromium.chrome.browser.crypto_wallet.util.PendingTxHelper; import org.chromium.chrome.browser.crypto_wallet.util.PortfolioHelper; import org.chromium.chrome.browser.crypto_wallet.util.SmoothLineChartEquallySpaced; @@ -67,9 +71,12 @@ import org.chromium.content_public.browser.UiThreadTaskTraits; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; + public class PortfolioFragment extends Fragment implements OnWalletListItemClick, ApprovedTxObserver { private static final String TAG = "PortfolioFragment"; @@ -98,6 +105,8 @@ public class PortfolioFragment private PortfolioModel mPortfolioModel; private ProgressBar mPbAssetDiscovery; private List mNftDataModels; + private List mAllNetworkInfos; + private NetworkSelectorModel mNetworkSelectionModel; public static PortfolioFragment newInstance() { return new PortfolioFragment(); @@ -168,8 +177,7 @@ public boolean onTouch(View v, MotionEvent event) { mBtnChangeNetwork.setOnLongClickListener(v -> { NetworkInfo networkInfo = null; if (mWalletModel != null) { - networkInfo = - mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork.getValue(); + networkInfo = mNetworkInfo; } if (networkInfo != null) { Toast.makeText(requireContext(), networkInfo.chainName, Toast.LENGTH_SHORT).show(); @@ -189,25 +197,34 @@ public void onClick(View v) { } private void setUpObservers() { - mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork.observe( - getViewLifecycleOwner(), networkInfo -> { - if (networkInfo == null) return; - if (mNetworkInfo != null && !mNetworkInfo.chainId.equals(networkInfo.chainId)) { - // clean up list to avoid user clicking on an asset of the previously + mWalletModel.getCryptoModel().getNetworkModel().mCryptoNetworks.observe( + getViewLifecycleOwner(), + allNetworkInfos -> { mAllNetworkInfos = allNetworkInfos; }); + mPortfolioModel.mNftModels.observe(getViewLifecycleOwner(), nftDataModels -> { + if (mPortfolioHelper == null) return; + mNftDataModels = nftDataModels; + setUpNftList(nftDataModels, mPortfolioHelper.getPerTokenCryptoSum(), + mPortfolioHelper.getPerTokenFiatSum()); + }); + mNetworkSelectionModel = + mWalletModel.getCryptoModel().getNetworkModel().openNetworkSelectorModel( + PortfolioFragment.TAG, NetworkSelectorModel.Mode.LOCAL_NETWORK_FILTER, + getLifecycle()); + // Show pending transactions fab to process pending txs + mNetworkSelectionModel.getSelectedNetwork().observe( + getViewLifecycleOwner(), localNetworkInfo -> { + if (localNetworkInfo == null) return; + if (mNetworkInfo != null + && !mNetworkInfo.chainId.equals(localNetworkInfo.chainId)) { + // Clean up list to avoid user clicking on an asset of the previously // selected network after the network has been changed clearAssets(); } - mNetworkInfo = networkInfo; - mBtnChangeNetwork.setText(Utils.getShortNameOfNetwork(networkInfo.chainName)); + mNetworkInfo = localNetworkInfo; + mBtnChangeNetwork.setText( + Utils.getShortNameOfNetwork(localNetworkInfo.chainName)); updatePortfolioGetPendingTx(); }); - mPortfolioModel.mNftModels.observe(getViewLifecycleOwner(), nftDataModels -> { - if (nftDataModels.isEmpty() || mPortfolioModel.mPortfolioHelper == null) return; - mNftDataModels = nftDataModels; - setUpNftList(nftDataModels, mPortfolioModel.mPortfolioHelper.getPerTokenCryptoSum(), - mPortfolioModel.mPortfolioHelper.getPerTokenFiatSum()); - }); - // Show pending transactions fab to process pending txs mWalletModel.getCryptoModel().getPendingTransactions().observe( getViewLifecycleOwner(), transactionInfos -> { mPendingTxs = transactionInfos; @@ -295,6 +312,14 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat initNftUi(view); } + @Override + public void onDestroyView() { + super.onDestroyView(); + if (mPortfolioModel != null) { + mPortfolioModel.clear(); + } + } + private void initNftUi(View root) { TextView editVisibleNft = root.findViewById(R.id.edit_visible_nfts); mRvNft = root.findViewById(R.id.rv_nft); @@ -305,12 +330,13 @@ private void initNftUi(View root) { editVisibleNft.setOnClickListener(v -> { onEditVisibleAssetsClick(); }); } - private void setUpCoinList(BlockchainToken[] userAssets, - HashMap perTokenCryptoSum, HashMap perTokenFiatSum) { + private void setUpCoinList(List userAssets, + HashMap perTokenCryptoSum, HashMap perTokenFiatSum, + List networkInfos) { String tokensPath = BlockchainRegistryFactory.getInstance().getTokensIconsLocation(); - mWalletCoinAdapter = Utils.setupVisibleAssetList( - userAssets, perTokenCryptoSum, perTokenFiatSum, tokensPath); + mWalletCoinAdapter = Utils.setupVisibleAssetList(userAssets, perTokenCryptoSum, + perTokenFiatSum, tokensPath, getResources(), networkInfos); mWalletCoinAdapter.setOnWalletListItemClick(PortfolioFragment.this); mRvCoins.setAdapter(mWalletCoinAdapter); mRvCoins.setLayoutManager(new LinearLayoutManager(getActivity())); @@ -323,10 +349,11 @@ private void setUpNftList(List nftDataModels, } else { AndroidUtils.show(mNftContainer, mTvNftTitle); } + String tokensPath = BlockchainRegistryFactory.getInstance().getTokensIconsLocation(); - mWalletNftAdapter = Utils.setupVisibleNftAssetList( - nftDataModels, perTokenCryptoSum, perTokenFiatSum, tokensPath); + mWalletNftAdapter = Utils.setupVisibleNftAssetList(nftDataModels, perTokenCryptoSum, + perTokenFiatSum, tokensPath, getResources(), mAllNetworkInfos); mWalletNftAdapter.setOnWalletListItemClick(PortfolioFragment.this); mRvNft.setAdapter(mWalletNftAdapter); mRvNft.setLayoutManager(new LinearLayoutManager(getActivity())); @@ -339,6 +366,7 @@ private void clearAssets() { if (mWalletNftAdapter != null) { mWalletNftAdapter.clear(); } + mBalance.setText(getString(R.string.brave_wallet_portfolio_balance_updating)); AndroidUtils.gone(mTvNftTitle, mNftContainer); } @@ -347,7 +375,7 @@ public void onAssetClick(BlockchainToken asset) { NetworkInfo selectedNetwork = null; if (mWalletModel != null) { selectedNetwork = - mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork.getValue(); + mWalletModel.getCryptoModel().getNetworkModel().getNetwork(asset.chainId); } if (selectedNetwork == null) { return; @@ -371,7 +399,8 @@ public void onAssetClick(BlockchainToken asset) { private void openNetworkSelection() { try { BraveActivity activity = BraveActivity.getBraveActivity(); - activity.openNetworkSelection(); + activity.openNetworkSelection( + NetworkSelectorModel.Mode.LOCAL_NETWORK_FILTER, PortfolioFragment.TAG); } catch (ActivityNotFoundException e) { Log.e(TAG, "openNetworkSelection " + e); } @@ -432,59 +461,44 @@ private void updatePortfolioGraph() { private void updatePortfolioGetPendingTx() { if (mWalletModel == null) return; - final NetworkInfo selectedNetwork = - mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork.getValue(); + final NetworkInfo selectedNetwork = mNetworkInfo; if (selectedNetwork == null) { return; } - mWalletModel.getKeyringModel().getKeyringPerId( - AssetUtils.getKeyringForCoinType(selectedNetwork.coin), keyringInfo -> { - if (keyringInfo == null) return; - AccountInfo[] accountInfos = new AccountInfo[] {}; - if (keyringInfo != null) { - accountInfos = keyringInfo.accountInfos; + + Activity activity = getActivity(); + if (!(activity instanceof BraveWalletActivity)) return; + mPortfolioModel.fetchAssets( + selectedNetwork, (BraveWalletBaseActivity) activity, (portfolioHelper) -> { + if (!AndroidUtils.canUpdateFragmentUi(this)) return; + mPortfolioHelper = portfolioHelper; + + final String fiatSumString = String.format( + Locale.getDefault(), "$%,.2f", mPortfolioHelper.getTotalFiatSum()); + + mFiatSumString = fiatSumString; + mBalance.setText(mFiatSumString); + mBalance.invalidate(); + + List tokens = new ArrayList<>(); + List nfts = new ArrayList<>(); + + for (BlockchainToken token : mPortfolioHelper.getUserAssetList()) { + if (token.isErc721 || token.isNft) { + nfts.add(token); + } else { + tokens.add(token); + } } - Activity activity = getActivity(); - final AccountInfo[] accountInfosFinal = accountInfos; - if (!(activity instanceof BraveWalletActivity)) return; + LiveDataUtil.observeOnce( mWalletModel.getCryptoModel().getNetworkModel().mCryptoNetworks, - allNetworks -> { - mPortfolioHelper = - new PortfolioHelper((BraveWalletActivity) activity, - allNetworks, accountInfosFinal); - - mPortfolioHelper.setSelectedNetwork(selectedNetwork); - mPortfolioHelper.calculateBalances(() -> { - final String fiatSumString = String.format(Locale.getDefault(), - "$%,.2f", mPortfolioHelper.getTotalFiatSum()); - PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> { - mFiatSumString = fiatSumString; - mBalance.setText(mFiatSumString); - mBalance.invalidate(); - - List tokens = new ArrayList<>(); - List nfts = new ArrayList<>(); - - for (BlockchainToken token : - mPortfolioHelper.getUserAssets()) { - if (token.isErc721 || token.isNft) { - nfts.add(token); - } else { - tokens.add(token); - } - } - - setUpCoinList(tokens.toArray(new BlockchainToken[0]), - mPortfolioHelper.getPerTokenCryptoSum(), - mPortfolioHelper.getPerTokenFiatSum()); - - mPortfolioModel.prepareNftListMetaData( - nfts, mNetworkInfo, mPortfolioHelper); - updatePortfolioGraph(); - }); - }); + networkInfos -> { + setUpCoinList(tokens, mPortfolioHelper.getPerTokenCryptoSum(), + mPortfolioHelper.getPerTokenFiatSum(), networkInfos); }); + mPortfolioModel.prepareNftListMetaData(nfts, mNetworkInfo, mPortfolioHelper); + updatePortfolioGraph(); }); } @@ -515,16 +529,25 @@ public void callAnotherApproveDialog() { } private void onEditVisibleAssetsClick() { - JsonRpcService jsonRpcService = getJsonRpcService(); - assert jsonRpcService != null; - NetworkInfo selectedNetwork = null; - if (mWalletModel != null) { - selectedNetwork = - mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork.getValue(); - } + NetworkInfo selectedNetwork = mNetworkInfo; if (selectedNetwork == null) { return; } + // TODO(pav): Remove this workaround once all network option is supported by + // EditVisibleAssetsBottomSheetDialogFragment. This workaround is to show default network + // assets in EditVisibleAssetsBottomSheetDialogFragment when "All Networks" option is + // selected + if (selectedNetwork.chainId.equals( + NetworkUtils.getAllNetworkOption(getContext()).chainId)) { + LiveDataUtil.observeOnce( + mWalletModel.getCryptoModel().getNetworkModel().mDefaultNetwork, + defaultNetwork -> { showEditVisibleDialog(defaultNetwork); }); + return; + } + showEditVisibleDialog(selectedNetwork); + } + + private void showEditVisibleDialog(NetworkInfo selectedNetwork) { EditVisibleAssetsBottomSheetDialogFragment bottomSheetDialogFragment = EditVisibleAssetsBottomSheetDialogFragment.newInstance( WalletCoinAdapter.AdapterType.EDIT_VISIBLE_ASSETS_LIST); diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/model/WalletListItemModel.java b/android/java/org/chromium/chrome/browser/crypto_wallet/model/WalletListItemModel.java index 9c7e179201a8..771a1bb34890 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/model/WalletListItemModel.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/model/WalletListItemModel.java @@ -10,6 +10,7 @@ import org.chromium.brave_wallet.mojom.AccountInfo; import org.chromium.brave_wallet.mojom.BlockchainToken; +import org.chromium.brave_wallet.mojom.NetworkInfo; import org.chromium.brave_wallet.mojom.TransactionInfo; import org.chromium.chrome.browser.app.domain.PortfolioModel; import org.chromium.chrome.browser.crypto_wallet.util.Utils; @@ -36,6 +37,8 @@ public class WalletListItemModel { private String mChainSymbol; private int mChainDecimals; private PortfolioModel.NftDataModel mNftDataModel; + private NetworkInfo mAssetNetwork; + private String mBrowserResPath; public WalletListItemModel( int icon, String title, String subTitle, String id, String text1, String text2) { @@ -207,4 +210,30 @@ public PortfolioModel.NftDataModel getNftDataModel() { public void setNftDataModel(PortfolioModel.NftDataModel mNftDataModel) { this.mNftDataModel = mNftDataModel; } + + public NetworkInfo getAssetNetwork() { + return mAssetNetwork; + } + + public void setAssetNetwork(NetworkInfo mAssetNetwork) { + this.mAssetNetwork = mAssetNetwork; + } + + public boolean isNativeAsset() { + if (mAssetNetwork == null || mBlockchainToken == null) return false; + return Utils.isNativeToken(mAssetNetwork, mBlockchainToken); + } + + public void setBrowserResourcePath(String resPath) { + mBrowserResPath = resPath; + } + + public String getBrowserResourcePath() { + return mBrowserResPath; + } + + public String getNetworkIcon() { + if (mAssetNetwork == null || TextUtils.isEmpty(getBrowserResourcePath())) return ""; + return "file://" + getBrowserResourcePath() + "/" + Utils.getNetworkIconName(mAssetNetwork); + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/observers/KeyringServiceObserverImpl.java b/android/java/org/chromium/chrome/browser/crypto_wallet/observers/KeyringServiceObserverImpl.java index cf0615976b20..31e8819c01ba 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/observers/KeyringServiceObserverImpl.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/observers/KeyringServiceObserverImpl.java @@ -8,6 +8,8 @@ import org.chromium.brave_wallet.mojom.KeyringServiceObserver; import org.chromium.mojo.system.MojoException; +import java.lang.ref.WeakReference; + public class KeyringServiceObserverImpl implements KeyringServiceObserver { public interface KeyringServiceObserverImplDelegate { default void locked() {} @@ -22,80 +24,60 @@ default void autoLockMinutesChanged() {} default void selectedAccountChanged(int coin) {} } - private KeyringServiceObserverImplDelegate mDelegate; + private WeakReference mDelegate; public KeyringServiceObserverImpl(KeyringServiceObserverImplDelegate delegate) { - mDelegate = delegate; + mDelegate = new WeakReference<>(delegate); } @Override public void keyringCreated(String keyringId) { - if (mDelegate == null) return; - - mDelegate.keyringCreated(keyringId); + if (isActive()) getRef().keyringCreated(keyringId); } @Override public void keyringRestored(String keyringId) { - if (mDelegate == null) return; - - mDelegate.keyringRestored(keyringId); + if (isActive()) getRef().keyringRestored(keyringId); } @Override public void keyringReset() { - if (mDelegate == null) return; - - mDelegate.keyringReset(); + if (isActive()) getRef().keyringReset(); } @Override public void locked() { - if (mDelegate == null) return; - - mDelegate.locked(); + if (isActive()) getRef().locked(); } @Override public void unlocked() { - if (mDelegate == null) return; - - mDelegate.unlocked(); + if (isActive()) getRef().unlocked(); } @Override public void backedUp() { - if (mDelegate == null) return; - - mDelegate.backedUp(); + if (isActive()) getRef().backedUp(); } @Override public void accountsChanged() { - if (mDelegate == null) return; - - mDelegate.accountsChanged(); + if (isActive()) getRef().accountsChanged(); } @Override public void accountsAdded(int coin, String[] addresses) { - if (mDelegate == null) return; - - mDelegate.accountsAdded(coin, addresses); + if (isActive()) getRef().accountsAdded(coin, addresses); } @Override public void autoLockMinutesChanged() { - if (mDelegate == null) return; - - mDelegate.autoLockMinutesChanged(); + if (isActive()) getRef().autoLockMinutesChanged(); } @Override public void selectedAccountChanged(int coin) { - if (mDelegate == null) return; - - mDelegate.selectedAccountChanged(coin); + if (isActive()) getRef().selectedAccountChanged(coin); } @Override @@ -106,7 +88,11 @@ public void close() { @Override public void onConnectionError(MojoException e) {} - public void destroy() { - mDelegate = null; + private KeyringServiceObserverImplDelegate getRef() { + return mDelegate.get(); + } + + private boolean isActive() { + return mDelegate != null && mDelegate.get() != null; } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java index ad1e1b00f897..2be569148646 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java @@ -16,6 +16,7 @@ import android.widget.TextView; import androidx.annotation.IdRes; +import androidx.fragment.app.Fragment; import org.chromium.chrome.R; @@ -98,4 +99,14 @@ private static void setViewVisibility(boolean isVisible, View... views) { public static int getScreenHeight() { return Resources.getSystem().getDisplayMetrics().heightPixels; } + + /** + * Check if the fragment is safe to update its UI + * @param frag instance + * @return true if Fragment UI can be updated otherwise false + */ + public static boolean canUpdateFragmentUi(Fragment frag) { + return !(frag.isRemoving() || frag.getActivity() == null || frag.isDetached() + || !frag.isAdded() || frag.getView() == null); + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/AssetUtils.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/AssetUtils.java index 373d519d6055..21fafc1fd769 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/AssetUtils.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/AssetUtils.java @@ -198,6 +198,11 @@ public static boolean isBatToken(BlockchainToken token) { put(BraveWalletConstants.SOLANA_MAINNET, Arrays.asList("sol")); put(BraveWalletConstants.FILECOIN_MAINNET, Arrays.asList("fil")); put(BraveWalletConstants.AVALANCHE_MAINNET_CHAIN_ID, Arrays.asList("avax", "avaxc")); + // Test Net + put(BraveWalletConstants.GOERLI_CHAIN_ID, Arrays.asList("eth")); + put(BraveWalletConstants.SEPOLIA_CHAIN_ID, Arrays.asList("eth")); + put(BraveWalletConstants.SOLANA_TESTNET, Arrays.asList("sol")); + put(BraveWalletConstants.SOLANA_DEVNET, Arrays.asList("sol")); } }; public static String httpifyIpfsUrl(String url) { @@ -207,4 +212,12 @@ public static String httpifyIpfsUrl(String url) { ? trimedUrl.replace("ipfs://", "https://ipfs.io/ipfs/") : trimedUrl; } + + public static String assetRatioId(BlockchainToken token) { + if (!TextUtils.isEmpty(token.coingeckoId)) return token.coingeckoId; + if (BraveWalletConstants.MAINNET_CHAIN_ID.equals(token.chainId) + || TextUtils.isEmpty(token.contractAddress)) + return token.symbol; + return token.contractAddress; + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/BalanceHelper.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/BalanceHelper.java index b0df4f2de97b..f2345e36d85d 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/BalanceHelper.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/BalanceHelper.java @@ -185,7 +185,7 @@ public static void getBlockchainTokensBalances(JsonRpcService jsonRpcService, } public static void getP3ABalances(WeakReference activityRef, - NetworkInfo[] allNetworks, NetworkInfo selectedNetwork, + List allNetworks, NetworkInfo selectedNetwork, Callbacks.Callback1>> callback) { BraveWalletBaseActivity activity = activityRef.get(); if (activity == null || activity.isFinishing()) return; diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/JavaUtils.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/JavaUtils.java index 2dcb6716413b..639bf5862e41 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/JavaUtils.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/JavaUtils.java @@ -5,7 +5,10 @@ package org.chromium.chrome.browser.crypto_wallet.util; +import android.text.TextUtils; + import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Predicate; @@ -37,6 +40,10 @@ public static List filter(List list, Predicate filter) { return filteredList; } + public static List filter(T[] items, Predicate filter) { + return filter(Arrays.asList(items), filter); + } + public static T find(List list, Predicate predicate) { for (T item : list) { if (predicate.test(item)) { @@ -70,4 +77,29 @@ public static boolean anyNull(Object... items) { } return false; } + + @SafeVarargs + public static T[] asArray(T... items) { + return items; + } + + /** + * Returns a combined string with a separator or an empty string. Empty and null values are + * ignored. + * @param separator to append after each string. Pass null for no separator + * @param items of string values. + * @return a combined or empty string. + */ + public static String concatStrings(char separator, String... items) { + if (items == null) return ""; + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < items.length; i++) { + String item = items[i]; + if (TextUtils.isEmpty(item)) continue; + builder.append(item).append(separator); + } + // Delete separator after the last value + builder.deleteCharAt(builder.length() - 1); + return builder.toString(); + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/NetworkUtils.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/NetworkUtils.java index 50647a2aa263..f621342bee1c 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/NetworkUtils.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/NetworkUtils.java @@ -6,14 +6,29 @@ package org.chromium.chrome.browser.crypto_wallet.util; import android.content.Context; +import android.text.TextUtils; import org.chromium.brave_wallet.mojom.NetworkInfo; import org.chromium.chrome.R; import org.chromium.url.mojom.Url; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + public class NetworkUtils { private static NetworkInfo sAllNetworksOption; + public static class Filters { + public static boolean isSameNetwork(NetworkInfo network, String chainId, int coin) { + return network.chainId.equals(chainId) && network.coin == coin; + } + + public static boolean isSameNetwork(NetworkInfo network1, NetworkInfo network2) { + return isSameNetwork(network1, network2.chainId, network2.coin); + } + } + public static NetworkInfo getAllNetworkOption(Context context) { if (sAllNetworksOption == null) { NetworkInfo allNetworkInfo = new NetworkInfo(); @@ -32,4 +47,22 @@ public static NetworkInfo getAllNetworkOption(Context context) { } return sAllNetworksOption; } + + public static List nonTestNetwork(List networkInfos) { + if (networkInfos == null) return Collections.emptyList(); + return networkInfos.stream() + .filter(network -> !WalletConstants.KNOWN_TEST_CHAIN_IDS.contains(network.chainId)) + .collect(Collectors.toList()); + } + + /** + * Get the NetworkInfo object of given chainId + * @param networkInfos all networks + * @param chainId of network to be found + * @return found network or null + */ + public static NetworkInfo findNetwork(List networkInfos, String chainId) { + if (networkInfos.isEmpty() || TextUtils.isEmpty(chainId)) return null; + return JavaUtils.find(networkInfos, networkInfo -> networkInfo.chainId.equals(chainId)); + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/PortfolioHelper.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/PortfolioHelper.java index 0e2a931fb594..c5680d524d78 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/PortfolioHelper.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/PortfolioHelper.java @@ -5,51 +5,57 @@ package org.chromium.chrome.browser.crypto_wallet.util; -import static org.chromium.chrome.browser.crypto_wallet.util.Utils.fromHexWei; +import static java.util.stream.Collectors.toMap; -import org.chromium.base.Log; import org.chromium.brave_wallet.mojom.AccountInfo; -import org.chromium.brave_wallet.mojom.AssetPrice; -import org.chromium.brave_wallet.mojom.AssetPriceTimeframe; import org.chromium.brave_wallet.mojom.AssetTimePrice; import org.chromium.brave_wallet.mojom.BlockchainToken; -import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.NetworkInfo; -import org.chromium.brave_wallet.mojom.ProviderError; import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletBaseActivity; -import org.chromium.chrome.browser.crypto_wallet.util.AssetsPricesHelper; -import org.chromium.chrome.browser.crypto_wallet.util.AsyncUtils; -import org.chromium.chrome.browser.crypto_wallet.util.BalanceHelper; -import org.chromium.chrome.browser.crypto_wallet.util.TokenUtils; -import org.chromium.chrome.browser.crypto_wallet.util.Utils; +import org.chromium.mojo.bindings.Callbacks; import org.chromium.mojo_base.mojom.TimeDelta; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public class PortfolioHelper { private static String TAG = "PortfolioHelper"; private final WeakReference mActivity; private NetworkInfo mSelectedNetwork; - private AccountInfo[] mAccountInfos; + private List mSelectedNetworks; + private final AccountInfo[] mAccountInfos; // Data supplied as result - private BlockchainToken[] mUserAssets; // aka selected assets + private final List + mUserAssetsList; // Aka visible assets, for multi-chain support private Double mTotalFiatSum; + // Always use Utils#tokenToString(BlockchainToken) to create key private HashMap mPerTokenFiatSum; private HashMap mPerTokenCryptoSum; private AssetTimePrice[] mFiatHistory; private int mFiatHistoryTimeframe; - private NetworkInfo[] mCryptoNetworks; + private final List mCryptoNetworks; + private Map mAssertSortPriorityPerCoinIndex; - public PortfolioHelper(BraveWalletBaseActivity activity, NetworkInfo[] cryptoNetworks, + public PortfolioHelper(BraveWalletBaseActivity activity, List cryptoNetworks, AccountInfo[] accountInfos) { mActivity = new WeakReference(activity); mCryptoNetworks = cryptoNetworks; mAccountInfos = accountInfos; + mSelectedNetworks = Collections.emptyList(); + mUserAssetsList = new ArrayList<>(); + mAssertSortPriorityPerCoinIndex = Collections.emptyMap(); } public void setSelectedNetwork(NetworkInfo selectedNetwork) { @@ -61,8 +67,9 @@ public void setFiatHistoryTimeframe(int timeframe) { mFiatHistoryTimeframe = timeframe; } - public BlockchainToken[] getUserAssets() { - return mUserAssets; + // Multi chain asset list + public List getUserAssetList() { + return mUserAssetsList; } public Double getTotalFiatSum() { @@ -87,7 +94,7 @@ public boolean isFiatHistoryEmpty() { } for (AssetTimePrice assetTimePrice : mFiatHistory) { - if (Double.valueOf(assetTimePrice.price) > 0.001d) { + if (Double.parseDouble(assetTimePrice.price) > 0.001d) { return false; } } @@ -102,6 +109,80 @@ public Double getMostRecentFiatSum() { return Double.valueOf(mFiatHistory[mFiatHistory.length - 1].price); } + public void fetchAllAssetsAndDetails(Callbacks.Callback1 callback) { + resetResultData(); + if (mActivity.get() == null || mSelectedNetworks.isEmpty()) return; + int totalNetworks = mSelectedNetworks.size(); + AtomicInteger resCount = new AtomicInteger(); + List assetAccountsNetworkBalances = new ArrayList<>(); + + for (NetworkInfo networkInfo : mSelectedNetworks) { + List accountInfosPerCoin = JavaUtils.filter( + mAccountInfos, accountInfo -> accountInfo.coin == networkInfo.coin); + Utils.getTxExtraInfo(mActivity, mCryptoNetworks, networkInfo, + accountInfosPerCoin.toArray(new AccountInfo[0]), null, true, + (assetPrices, userAssetsList, nativeAssetsBalances, + blockchainTokensBalances) -> { + mUserAssetsList.addAll(Arrays.asList(userAssetsList)); + + AssetAccountsNetworkBalance asset = new AssetAccountsNetworkBalance( + assetPrices, userAssetsList, nativeAssetsBalances, + blockchainTokensBalances, networkInfo, accountInfosPerCoin); + assetAccountsNetworkBalances.add(asset); + + // Calculate balances only after the responses of network list are fetched + if (resCount.incrementAndGet() == totalNetworks) { + mUserAssetsList.sort(Comparator.comparing( + token -> mAssertSortPriorityPerCoinIndex.get(token.chainId))); + for (AssetAccountsNetworkBalance assetAccountsNetworkBalance : + assetAccountsNetworkBalances) { + // Sum across accounts + for (AccountInfo accountInfo : + assetAccountsNetworkBalance.accountInfos) { + final String accountAddressLower = + accountInfo.address.toLowerCase(Locale.getDefault()); + HashMap thisAccountTokensBalances = + Utils.getOrDefault(assetAccountsNetworkBalance + .blockchainTokensBalances, + accountAddressLower, + new HashMap()); + for (BlockchainToken userAsset : + assetAccountsNetworkBalance.userAssetsList) { + String currentAssetKey = Utils.tokenToString(userAsset); + double prevTokenCryptoBalanceSum = Utils.getOrDefault( + mPerTokenCryptoSum, currentAssetKey, 0.0d); + final double thisCryptoBalance = + Utils.isNativeToken( + assetAccountsNetworkBalance.networkInfo, + userAsset) + ? Utils.getOrDefault(assetAccountsNetworkBalance + .nativeAssetsBalances, + accountAddressLower, 0.0d) + : Utils.getOrDefault(thisAccountTokensBalances, + currentAssetKey, 0.0d); + mPerTokenCryptoSum.put(currentAssetKey, + prevTokenCryptoBalanceSum + thisCryptoBalance); + + double prevTokenFiatBalanceSum = Utils.getOrDefault( + mPerTokenFiatSum, currentAssetKey, 0.0d); + double thisTokenPrice = Utils.getOrDefault( + assetAccountsNetworkBalance.assetPrices, + userAsset.symbol.toLowerCase(Locale.getDefault()), + 0.0d); + double thisFiatBalance = thisTokenPrice * thisCryptoBalance; + mPerTokenFiatSum.put(currentAssetKey, + prevTokenFiatBalanceSum + thisFiatBalance); + + mTotalFiatSum += thisFiatBalance; + } + } + } + callback.call(this); + } + }); + } + } + public void calculateBalances(Runnable runWhenDone) { resetResultData(); if (mActivity.get() == null || mActivity.get().isFinishing()) return; @@ -109,16 +190,15 @@ public void calculateBalances(Runnable runWhenDone) { Utils.getTxExtraInfo(mActivity, mCryptoNetworks, mSelectedNetwork, mAccountInfos, null, true, (assetPrices, userAssetsList, nativeAssetsBalances, blockchainTokensBalances) -> { - mUserAssets = userAssetsList; - - // Sum accross accounts + mUserAssetsList.addAll(Arrays.asList(userAssetsList)); + // Sum across accounts for (AccountInfo accountInfo : mAccountInfos) { final String accountAddressLower = accountInfo.address.toLowerCase(Locale.getDefault()); HashMap thisAccountTokensBalances = Utils.getOrDefault(blockchainTokensBalances, accountAddressLower, new HashMap()); - for (BlockchainToken userAsset : mUserAssets) { + for (BlockchainToken userAsset : mUserAssetsList) { String currentAssetKey = Utils.tokenToString(userAsset); double prevTokenCryptoBalanceSum = Utils.getOrDefault(mPerTokenCryptoSum, currentAssetKey, 0.0d); @@ -148,11 +228,11 @@ public void calculateBalances(Runnable runWhenDone) { } private void resetResultData() { - mUserAssets = new BlockchainToken[0]; mTotalFiatSum = 0.0d; mPerTokenFiatSum = new HashMap(); mPerTokenCryptoSum = new HashMap(); mFiatHistory = new AssetTimePrice[0]; + mUserAssetsList.clear(); } private AssetTimePrice getZeroHistoryEntry(long microseconds) { @@ -173,14 +253,21 @@ private AssetTimePrice[] getZeroPortfolioHistory() { public void calculateFiatHistory(Runnable runWhenDone) { mFiatHistory = new AssetTimePrice[0]; + var nonZeroBalanceAssetList = + mUserAssetsList.stream() + .filter(token -> { + var assetBalance = mPerTokenCryptoSum.get(Utils.tokenToString(token)); + return assetBalance != null && assetBalance > 0; + }) + .collect(Collectors.toList()); AsyncUtils.MultiResponseHandler historyMultiResponse = - new AsyncUtils.MultiResponseHandler(mUserAssets.length); + new AsyncUtils.MultiResponseHandler(nonZeroBalanceAssetList.size()); ArrayList pricesHistoryContexts = new ArrayList(); - for (BlockchainToken userAsset : mUserAssets) { + for (BlockchainToken userAsset : nonZeroBalanceAssetList) { AsyncUtils.GetPriceHistoryResponseContext priceHistoryContext = new AsyncUtils.GetPriceHistoryResponseContext( historyMultiResponse.singleResponseComplete); @@ -188,10 +275,11 @@ public void calculateFiatHistory(Runnable runWhenDone) { priceHistoryContext.userAsset = userAsset; pricesHistoryContexts.add(priceHistoryContext); - if (mActivity.get() != null && !mActivity.get().isFinishing()) + if (mActivity.get() != null && !mActivity.get().isFinishing()) { mActivity.get().getAssetRatioService().getPriceHistory( - userAsset.symbol.toLowerCase(Locale.getDefault()), "usd", - mFiatHistoryTimeframe, priceHistoryContext); + AssetUtils.assetRatioId(userAsset), "usd", mFiatHistoryTimeframe, + priceHistoryContext); + } } historyMultiResponse.setWhenAllCompletedAction(() -> { @@ -219,8 +307,6 @@ public void calculateFiatHistory(Runnable runWhenDone) { return; } - assert !pricesHistoryContexts.isEmpty(); - AsyncUtils.GetPriceHistoryResponseContext shortestPriceHistoryContext = Collections.min(pricesHistoryContexts, (l, r) -> { return Integer.compare(l.timePrices.length, r.timePrices.length); @@ -240,9 +326,7 @@ public void calculateFiatHistory(Runnable runWhenDone) { pricesHistoryContexts) { thisDateFiatSum += Float.parseFloat(priceHistoryContext.timePrices[i].price) * Utils.getOrDefault(mPerTokenCryptoSum, - priceHistoryContext.userAsset.symbol.toLowerCase( - Locale.getDefault()), - 0.0d); + Utils.tokenToString(priceHistoryContext.userAsset), 0.0d); } mFiatHistory[i].price = Double.toString(thisDateFiatSum); } @@ -250,4 +334,39 @@ public void calculateFiatHistory(Runnable runWhenDone) { runWhenDone.run(); }); } + + public void setSelectedNetworks(List mSelectedNetworks) { + this.mSelectedNetworks = mSelectedNetworks; + updateAssetSortPriority(mSelectedNetworks); + } + + // Update map of Map, to be used for sorting "All Networks" + // asset list + private void updateAssetSortPriority(List networks) { + mAssertSortPriorityPerCoinIndex = + IntStream.range(0, networks.size()) + .boxed() + .collect(toMap(i -> networks.get(i).chainId, Function.identity())); + } + + public static class AssetAccountsNetworkBalance { + HashMap assetPrices; + BlockchainToken[] userAssetsList; + HashMap nativeAssetsBalances; + HashMap> blockchainTokensBalances; + NetworkInfo networkInfo; + List accountInfos; + + public AssetAccountsNetworkBalance(HashMap assetPrices, + BlockchainToken[] userAssetsList, HashMap nativeAssetsBalances, + HashMap> blockchainTokensBalances, + NetworkInfo networkInfo, List accountInfos) { + this.assetPrices = assetPrices; + this.userAssetsList = userAssetsList; + this.nativeAssetsBalances = nativeAssetsBalances; + this.blockchainTokensBalances = blockchainTokensBalances; + this.networkInfo = networkInfo; + this.accountInfos = accountInfos; + } + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/TokenUtils.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/TokenUtils.java index 990ce9a3bbf7..09b3296659a0 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/TokenUtils.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/TokenUtils.java @@ -13,8 +13,6 @@ import org.chromium.brave_wallet.mojom.BraveWalletService; import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.NetworkInfo; -import org.chromium.brave_wallet.mojom.OnRampProvider; -import org.chromium.chrome.browser.crypto_wallet.util.Utils; import org.chromium.mojo.bindings.Callbacks; import java.lang.UnsupportedOperationException; @@ -127,7 +125,7 @@ public static void getBuyTokensFiltered(BlockchainRegistry blockchainRegistry, BlockchainToken[] filteredTokens = filterTokens(selectedNetwork, tokens, tokenType, false); Arrays.sort(filteredTokens, blockchainTokenComparatorPerGasOrBatType); - callback.call(filteredTokens); + callback.call(removeDuplicates(filteredTokens)); }); } @@ -219,4 +217,27 @@ else if (!isBatToken1 && isBatToken2) else return token1.symbol.compareTo(token2.symbol); }; + + // Returns an array of available assets per ramp provider and removes duplicates with the + // same contract address using case insensitive contractAddress comparison. + private static BlockchainToken[] removeDuplicates(BlockchainToken[] walletListItemModelList) { + List result = new ArrayList<>(); + for (BlockchainToken item : walletListItemModelList) { + String contractAddress = item.contractAddress; + boolean duplicate = false; + for (BlockchainToken itemResult : result) { + // IMPORTANT: use `equalsIgnoreCase` to detect two contract addresses with different + // capitalization. + if (contractAddress.equalsIgnoreCase(itemResult.contractAddress)) { + duplicate = true; + break; + } + } + // Do not add duplicated item. + if (!duplicate) { + result.add(item); + } + } + return result.toArray(new BlockchainToken[0]); + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/Utils.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/Utils.java index fd09394b9737..ddf2238f0b2a 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/Utils.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/Utils.java @@ -73,7 +73,6 @@ import org.chromium.brave_wallet.mojom.BraveWalletService; import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.JsonRpcService; -import org.chromium.brave_wallet.mojom.KeyringService; import org.chromium.brave_wallet.mojom.NetworkInfo; import org.chromium.brave_wallet.mojom.ProviderError; import org.chromium.brave_wallet.mojom.TransactionInfo; @@ -88,7 +87,6 @@ import org.chromium.chrome.browser.crypto_wallet.activities.AssetDetailActivity; import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletBaseActivity; import org.chromium.chrome.browser.crypto_wallet.activities.BuySendSwapActivity; -import org.chromium.chrome.browser.crypto_wallet.activities.NftDetailActivity; import org.chromium.chrome.browser.crypto_wallet.adapters.WalletCoinAdapter; import org.chromium.chrome.browser.crypto_wallet.fragments.ApproveTxBottomSheetDialogFragment; import org.chromium.chrome.browser.crypto_wallet.listeners.OnWalletListItemClick; @@ -250,20 +248,23 @@ public static void hideKeyboard(Activity activity) { if (focusedView != null) imm.hideSoftInputFromWindow(focusedView.getWindowToken(), 0); } + public static void openBuySendSwapActivity( + Activity activity, BuySendSwapActivity.ActivityType activityType) { + openBuySendSwapActivity(activity, activityType, null, null); + } + public static void openBuySendSwapActivity(Activity activity, - BuySendSwapActivity.ActivityType activityType, String swapFromAssetSymbol) { + BuySendSwapActivity.ActivityType activityType, String swapFromAssetSymbol, + String chainId) { assert activity != null; Intent buySendSwapActivityIntent = new Intent(activity, BuySendSwapActivity.class); - buySendSwapActivityIntent.putExtra("activityType", activityType.getValue()); - buySendSwapActivityIntent.putExtra("swapFromAssetSymbol", swapFromAssetSymbol); + buySendSwapActivityIntent.putExtra( + BuySendSwapActivity.ACTIVITY_TYPE, activityType.getValue()); + buySendSwapActivityIntent.putExtra(BuySendSwapActivity.ASSET_SYMBOL, swapFromAssetSymbol); + buySendSwapActivityIntent.putExtra(BuySendSwapActivity.ASSET_CHAIN_ID, chainId); activity.startActivity(buySendSwapActivityIntent); } - public static void openBuySendSwapActivity( - Activity activity, BuySendSwapActivity.ActivityType activityType) { - openBuySendSwapActivity(activity, activityType, null); - } - public static void openAssetDetailsActivity( Activity activity, String chainId, BlockchainToken asset) { assert activity != null; @@ -286,6 +287,7 @@ public static void openAssetDetailsActivity( */ public static String getShortNameOfNetwork(String networkName) { if (!TextUtils.isEmpty(networkName)) { + if (networkName.length() < 14) return networkName; String firstWord = networkName.split(" ")[0]; if (firstWord.length() > 18) { return firstWord.substring(0, 16) + ".."; @@ -968,39 +970,7 @@ public static String getTimeframeString(int assetPriceTimeframe) { public static BlockchainToken makeNetworkAsset(NetworkInfo network) { String logo; - // TODO: Add missing logos - // case "SOL": - // logo = "sol.png"; - // break; - // case "FIL": - // logo = "fil.png"; - // break; - // case network.chainId === BraveWallet.OPTIMISM_MAINNET_CHAIN_ID: - // logo = "optimism.png"; - // break; - // case network.chainId === BraveWallet.AVALANCHE_MAINNET_CHAIN_ID: - // logo = "avax.png"; - // break; - // case network.chainId === BraveWallet.FANTOM_MAINNET_CHAIN_ID: - // logo = "fantom.png"; - // break; - // case network.chainId === BraveWallet.CELO_MAINNET_CHAIN_ID: - // logo = "celo.png"; - // break; - if (network.chainId.equals(BraveWalletConstants.MAINNET_CHAIN_ID)) { - logo = "eth.png"; - } else if (network.chainId.equals(BraveWalletConstants.POLYGON_MAINNET_CHAIN_ID)) { - logo = "matic.png"; - } else if (network.chainId.equals( - BraveWalletConstants.BINANCE_SMART_CHAIN_MAINNET_CHAIN_ID)) { - logo = "bnb.png"; - } else if (network.chainId.equals(BraveWalletConstants.SOLANA_MAINNET) - || network.chainId.equals(BraveWalletConstants.SOLANA_TESTNET) - || network.chainId.equals(BraveWalletConstants.SOLANA_DEVNET)) { - logo = "sol.png"; - } else { - logo = "eth.png"; - } + logo = getNetworkIconName(network); BlockchainToken asset = new BlockchainToken(); asset.name = network.symbolName; @@ -1017,6 +987,36 @@ public static BlockchainToken makeNetworkAsset(NetworkInfo network) { return asset; } + @NonNull + public static String getNetworkIconName(NetworkInfo network) { + String logo; + switch (network.chainId) { + case BraveWalletConstants.MAINNET_CHAIN_ID: + logo = "eth.png"; + break; + case BraveWalletConstants.POLYGON_MAINNET_CHAIN_ID: + logo = "matic.png"; + break; + case BraveWalletConstants.BINANCE_SMART_CHAIN_MAINNET_CHAIN_ID: + logo = "bnb.png"; + break; + case BraveWalletConstants.SOLANA_MAINNET: + case BraveWalletConstants.SOLANA_TESTNET: + case BraveWalletConstants.SOLANA_DEVNET: + logo = "sol.png"; + break; + case BraveWalletConstants.AVALANCHE_MAINNET_CHAIN_ID: + logo = "avax.png"; + break; + case BraveWalletConstants.CELO_MAINNET_CHAIN_ID: + logo = "celo.png"; + break; + default: + logo = "eth.png"; + } + return logo; + } + // Replace USDC and DAI contract addresses for Goerli network public static BlockchainToken[] fixupTokensRegistry(BlockchainToken[] tokens, String chainId) { for (BlockchainToken token : tokens) { @@ -1434,16 +1434,16 @@ public static org.chromium.url.internal.mojom.Origin getCurrentMojomOrigin() { return hostOrigin; } - public static WalletCoinAdapter setupVisibleAssetList(BlockchainToken[] userAssets, + public static WalletCoinAdapter setupVisibleAssetList(List userAssets, HashMap perTokenCryptoSum, HashMap perTokenFiatSum, - String tokensPath) { + String tokensPath, Resources resources, List allNetworkInfos) { WalletCoinAdapter walletCoinAdapter = new WalletCoinAdapter(WalletCoinAdapter.AdapterType.VISIBLE_ASSETS_LIST); List walletListItemModelList = new ArrayList<>(); for (BlockchainToken userAsset : userAssets) { - WalletListItemModel walletListItemModel = mapToWalletListItemModel( - perTokenCryptoSum, perTokenFiatSum, tokensPath, userAsset); + WalletListItemModel walletListItemModel = mapToWalletListItemModel(perTokenCryptoSum, + perTokenFiatSum, tokensPath, userAsset, resources, allNetworkInfos); walletListItemModelList.add(walletListItemModel); } @@ -1455,14 +1455,15 @@ public static WalletCoinAdapter setupVisibleAssetList(BlockchainToken[] userAsse public static WalletCoinAdapter setupVisibleNftAssetList( List userAssets, HashMap perTokenCryptoSum, - HashMap perTokenFiatSum, String tokensPath) { + HashMap perTokenFiatSum, String tokensPath, Resources resources, + List allNetworkInfos) { WalletCoinAdapter walletCoinAdapter = new WalletCoinAdapter(WalletCoinAdapter.AdapterType.VISIBLE_ASSETS_LIST); List walletListItemModelList = new ArrayList<>(); for (PortfolioModel.NftDataModel userAsset : userAssets) { - WalletListItemModel walletListItemModel = mapToWalletListItemModel( - perTokenCryptoSum, perTokenFiatSum, tokensPath, userAsset.token); + WalletListItemModel walletListItemModel = mapToWalletListItemModel(perTokenCryptoSum, + perTokenFiatSum, tokensPath, userAsset.token, resources, allNetworkInfos); walletListItemModel.setNftDataModel(userAsset); walletListItemModelList.add(walletListItemModel); } @@ -1476,22 +1477,29 @@ public static WalletCoinAdapter setupVisibleNftAssetList( @NonNull private static WalletListItemModel mapToWalletListItemModel( HashMap perTokenCryptoSum, HashMap perTokenFiatSum, - String tokensPath, BlockchainToken userAsset) { + String tokensPath, BlockchainToken userAsset, Resources resources, + List allNetworkInfos) { String currentAssetKey = Utils.tokenToString(userAsset); Double fiatBalance = Utils.getOrDefault(perTokenFiatSum, currentAssetKey, 0.0d); String fiatBalanceString = String.format(Locale.getDefault(), "$%,.2f", fiatBalance); Double cryptoBalance = Utils.getOrDefault(perTokenCryptoSum, currentAssetKey, 0.0d); + NetworkInfo assetNetwork = NetworkUtils.findNetwork(allNetworkInfos, userAsset.chainId); + String subtitle = assetNetwork == null + ? userAsset.symbol + : resources.getString(R.string.brave_wallet_portfolio_asset_network_description, + userAsset.symbol, assetNetwork.chainName); String cryptoBalanceString = String.format(Locale.getDefault(), "%.4f %s", cryptoBalance, userAsset.symbol); - WalletListItemModel walletListItemModel = - new WalletListItemModel(Utils.getCoinIcon(userAsset.coin), userAsset.name, - userAsset.symbol, userAsset.tokenId, - // Amount in USD - fiatBalanceString, - // Amount in current crypto currency/token - cryptoBalanceString); + WalletListItemModel walletListItemModel = new WalletListItemModel( + Utils.getCoinIcon(userAsset.coin), userAsset.name, subtitle, userAsset.tokenId, + // Amount in USD + fiatBalanceString, + // Amount in current crypto currency/token + cryptoBalanceString); + walletListItemModel.setBrowserResourcePath(tokensPath); + walletListItemModel.setAssetNetwork(assetNetwork); walletListItemModel.setIconPath("file://" + tokensPath + "/" + userAsset.logo); walletListItemModel.setBlockchainToken(userAsset); return walletListItemModel; @@ -1511,15 +1519,16 @@ public static String formatErc721TokenTitle(String title, String id) { public static String tokenToString(BlockchainToken token) { if (token == null) return ""; - final String symbolLowerCase = token.symbol.toLowerCase(Locale.getDefault()); - return token.isErc721 ? Utils.formatErc721TokenTitle(symbolLowerCase, token.tokenId) - : symbolLowerCase; + final String symbolLowerCase = token.symbol.toLowerCase(Locale.ENGLISH); + final String contractAddress = token.contractAddress.toLowerCase(Locale.ENGLISH); + return JavaUtils.concatStrings( + '#', symbolLowerCase, contractAddress, token.tokenId, token.chainId); } // Please only use this function when you need all the info (tokens, prices and balances) at the // same time! public static void getTxExtraInfo(WeakReference activityRef, - NetworkInfo[] allNetworks, NetworkInfo selectedNetwork, AccountInfo[] accountInfos, + List allNetworks, NetworkInfo selectedNetwork, AccountInfo[] accountInfos, BlockchainToken[] filterByTokens, boolean userAssetsOnly, Callbacks.Callback4, BlockchainToken[], HashMap, HashMap>> callback) { @@ -1529,16 +1538,14 @@ public static void getTxExtraInfo(WeakReference activit BlockchainRegistry blockchainRegistry = activity.getBlockchainRegistry(); AssetRatioService assetRatioService = activity.getAssetRatioService(); JsonRpcService jsonRpcService = activity.getJsonRpcService(); - BraveWalletP3a braveWalletP3A = activity.getBraveWalletP3A(); assert braveWalletService != null && blockchainRegistry != null && assetRatioService != null - && jsonRpcService != null - && braveWalletP3A != null : "Invalid service initialization"; + && jsonRpcService != null : "Invalid service initialization"; - if (JavaUtils.anyNull(braveWalletService, blockchainRegistry, assetRatioService, - jsonRpcService, braveWalletP3A)) + if (JavaUtils.anyNull( + braveWalletService, blockchainRegistry, assetRatioService, jsonRpcService)) return; - AsyncUtils.MultiResponseHandler multiResponse = new AsyncUtils.MultiResponseHandler(4); + AsyncUtils.MultiResponseHandler multiResponse = new AsyncUtils.MultiResponseHandler(3); TokenUtils.getUserOrAllTokensFiltered(braveWalletService, blockchainRegistry, selectedNetwork, selectedNetwork.coin, TokenUtils.TokenType.ALL, userAssetsOnly, @@ -1570,36 +1577,19 @@ public static void getTxExtraInfo(WeakReference activit BalanceHelper.getBlockchainTokensBalances(jsonRpcService, selectedNetwork, accountInfos, tokens, getBlockchainTokensBalancesContext); - AsyncUtils.GetP3ABalancesContext getP3ABalancesContext = - new AsyncUtils.GetP3ABalancesContext( - multiResponse.singleResponseComplete); - BalanceHelper.getP3ABalances( - activityRef, allNetworks, selectedNetwork, getP3ABalancesContext); - multiResponse.setWhenAllCompletedAction(() -> { - // P3A active accounts - HashMap> activeAddresses = - getP3ABalancesContext.activeAddresses; - BalanceHelper.updateActiveAddresses( - new AsyncUtils.GetNativeAssetsBalancesResponseContext[] { - getNativeAssetsBalancesContext}, - new AsyncUtils.GetBlockchainTokensBalancesResponseContext[] { - getBlockchainTokensBalancesContext}, - activeAddresses); - for (int coinType : P3ACoinTypes) { - braveWalletP3A.recordActiveWalletCount( - activeAddresses.get(coinType).size(), coinType); - } - callback.call(fetchPricesContext.assetPrices, fullTokenList, getNativeAssetsBalancesContext.nativeAssetsBalances, getBlockchainTokensBalancesContext.blockchainTokensBalances); + logP3ARecords(JavaUtils.asArray(getNativeAssetsBalancesContext), + JavaUtils.asArray(getBlockchainTokensBalancesContext), activityRef, + allNetworks, selectedNetwork); }); }); } public static void getP3ANetworks( - NetworkInfo[] allNetworks, Callbacks.Callback1 callback) { + List allNetworks, Callbacks.Callback1 callback) { ArrayList relevantNetworks = new ArrayList(); boolean countTestNetworks = CommandLine.getInstance().hasSwitch( BraveWalletConstants.P3A_COUNT_TEST_NETWORKS_SWITCH); @@ -1706,4 +1696,36 @@ public static double parseDouble(String s) throws ParseException { return num.doubleValue(); } + + private static void logP3ARecords( + AsyncUtils.GetNativeAssetsBalancesResponseContext[] nativeAssetsBalancesResponses, + AsyncUtils + .GetBlockchainTokensBalancesResponseContext[] blockchainTokensBalancesResponses, + WeakReference activityRef, List allNetworks, + NetworkInfo selectedNetwork) { + BraveWalletBaseActivity activity = activityRef.get(); + if (activity == null || activity.isFinishing() + || JavaUtils.anyNull(activity.getBraveWalletP3A())) + return; + BraveWalletP3a braveWalletP3A = activity.getBraveWalletP3A(); + + AsyncUtils.MultiResponseHandler multiResponse = new AsyncUtils.MultiResponseHandler(1); + + AsyncUtils.GetP3ABalancesContext getP3ABalancesContext = + new AsyncUtils.GetP3ABalancesContext(multiResponse.singleResponseComplete); + BalanceHelper.getP3ABalances( + activityRef, allNetworks, selectedNetwork, getP3ABalancesContext); + + multiResponse.setWhenAllCompletedAction(() -> { + HashMap> activeAddresses = + getP3ABalancesContext.activeAddresses; + // P3A active accounts + BalanceHelper.updateActiveAddresses(nativeAssetsBalancesResponses, + blockchainTokensBalancesResponses, activeAddresses); + for (int coinType : P3ACoinTypes) { + braveWalletP3A.recordActiveWalletCount( + activeAddresses.get(coinType).size(), coinType); + } + }); + } } diff --git a/android/java/res/layout/edit_visible_assets_bottom_sheet.xml b/android/java/res/layout/edit_visible_assets_bottom_sheet.xml index 6882f5d57d80..964673d69bc4 100644 --- a/android/java/res/layout/edit_visible_assets_bottom_sheet.xml +++ b/android/java/res/layout/edit_visible_assets_bottom_sheet.xml @@ -1,36 +1,57 @@ + android:orientation="vertical" + tools:ignore="RtlSymmetry"> + android:layout_marginEnd="27dp" + android:inputType="textFilter" /> - + android:layout_marginVertical="4dp" + android:layout_marginHorizontal="20dp"> + + + + + + - + + + + + + Automatically loads eligible articles in reader mode + + BAT%1$s on Ethereum Mainnet%2$s +