From 3d452012de4fe15a9ea57ba43d7c63374ddfd527 Mon Sep 17 00:00:00 2001 From: James Brown Date: Wed, 27 Dec 2023 11:12:24 +1000 Subject: [PATCH] Fix TokenScript view (#3344) * Fix Active TokenScript view * Fix whitelist sites * Fix for TokenScript display and attribute values * Update dapps list * Fix for TokenScript view and refresh of attributes * update e2e --- .github/workflows/e2e.yml | 6 +- app/src/main/assets/dapps_list.json | 11 ++-- .../app/entity/StandardFunctionInterface.java | 2 + .../app/entity/TSAttrCallback.java | 10 ++++ .../alphawallet/app/entity/UpdateType.java | 8 +++ .../tokenscript/TokenscriptFunction.java | 56 ++++++++++++++++-- .../app/service/AssetDefinitionService.java | 31 ++++++---- .../app/ui/AdvancedSettingsActivity.java | 15 +++-- .../alphawallet/app/ui/FunctionActivity.java | 17 ++---- .../app/ui/NFTAssetDetailActivity.java | 59 ++++++++----------- .../app/viewmodel/TokenFunctionViewModel.java | 32 +++++++++- .../alphawallet/app/web3/Web3TokenView.java | 36 +++++++---- .../app/widget/FunctionButtonBar.java | 5 +- .../alphawallet/app/widget/NFTImageView.java | 9 ++- .../token/entity/TransactionResult.java | 2 +- 15 files changed, 201 insertions(+), 98 deletions(-) create mode 100644 app/src/main/java/com/alphawallet/app/entity/TSAttrCallback.java create mode 100644 app/src/main/java/com/alphawallet/app/entity/UpdateType.java diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index bebe23c6db..e078aa83ab 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -17,16 +17,16 @@ jobs: api-level: [28] target: [default] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v2 - name: Set up JDK - uses: actions/setup-java@v3 + uses: actions/setup-java@v2 with: distribution: oracle java-version: 17 architecture: x64 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v2 with: node-version: 16 cache: 'npm' diff --git a/app/src/main/assets/dapps_list.json b/app/src/main/assets/dapps_list.json index 339440f745..8e3a3a2da5 100644 --- a/app/src/main/assets/dapps_list.json +++ b/app/src/main/assets/dapps_list.json @@ -1,13 +1,14 @@ [ + {"name": "TokenScript", "description": "Smart Token Labs TokenScript Viewer", "url": "https://viewer.tokenscript.org/", "category": "Infrastructure"}, + {"name": "SmartLayer", "description": "Smart Token Labs Smart Layer Network", "url": "https://smartlayer.network", "category": "Infrastructure"}, + {"name": "X", "description": "Social Media", "url": "https://x.com", "category": "Social Media"}, {"name": "Aave", "description": "A decentralized non-custodial liquidity protocol where users can participate as depositors or borrowers", "url": "https://app.aave.com/", "category": "Finance"}, {"name": "Tbull", "description": "A Utility Token on Binance Smart Chain for Payments for Services", "url": "tbull.live", "category": "Utility"}, - {"name": "Rare Coin", "description": "Free Crypto Faucet & Yeild Farming", "url": "make.rare.claims", "category": "Tool"}, - {"name": "Eporio", "description": "The cheaper marketplace for NFT - Non Fungible Tokens", "url": "https://epor.io", "category": "Marketplace"}, + {"name": "Rare Coin", "description": "Free Crypto Faucet & Yield Farming", "url": "make.rare.claims", "category": "Tool"}, {"name": "DeFiBox", "description": "one-stop DeFi asset, data and protocols aggregation platform", "url": "https://www.defibox.com/index?utm_source=2189969", "category": "Tool"}, {"name": "TokenSets", "description": "Enhance your portfolio with automated asset management strategies.", "url": "https://www.tokensets.com/", "category": "Finance"}, {"name": "State of the ÐApps", "description": "Directory of Decentralized Applications", "url": "https://www.stateofthedapps.com/", "category": "Directory"}, {"name": "BulkSender", "description": "Batch sending of tokens", "url": "https://bulksender.app/", "category": "Finance"}, - {"name": "Loanscan", "description": "Get the best return for your Tokens", "url": "https://loanscan.io/", "category": "Finance"}, {"name": "Fuse Studio", "description": "Turning communities into economies", "url": "https://studio.fusenet.io/", "category": "Finance"}, {"name": "Axie Infinity", "description": "Collect and raise fantasy creatures", "url": "https://axieinfinity.com/", "category": "Game"}, {"name": "ChickenHunt", "description": "character-growing IDLE game", "url": "https://chickenhunt.io/", "category": "Game"}, @@ -15,7 +16,6 @@ {"name": "Dice2win", "description": "Simple and fair dice game", "url": "https://dice2.win/", "category": "Game"}, {"name": "Dragonereum", "description": "Own and trade dragons, fight with other players", "url": "https://dapp.dragonereum.io/", "category": "Game"}, {"name": "HyperDragons", "description": "Large scale strategy battle game", "url": "https://hyperdragons.alfakingdom.com/", "category": "Game"}, - {"name": "MoveCastle", "description": "Learn Libra Move through game", "url": "https://learnlibramove.com/", "category": "Game"}, {"name": "Last Trip", "description": "A RPG game", "url": "http://lasttrip.matrixdapp.com/", "category": "Game"}, {"name": "LORDLESS", "description": "Be a bounty hunter in my tavern", "url": "https://game.lordless.io/home", "category": "Game"}, {"name": "MLB Crypto Baseball", "description": "Baseball collectible game", "url": "https://mlbcryptobaseball.com", "category": "Game"}, @@ -51,13 +51,10 @@ {"name": "MakerDAO CDP Portal", "description": "Where you can interact with the Dai Credit System", "url": "https://cdp.makerdao.com/", "category": "Finance"}, {"name": "Nexo", "description": "Instant Crypto Loans", "url": "https://nexo.io/", "category": "Finance"}, {"name": "AirSwap", "description": "Peer-to-Peer trading on Ethereum", "url": "https://instant.airswap.io", "category": "Exchange"}, - {"name": "Chibi Fighters", "description": "Chibi Fighters are fierce little warriors that know no mercy", "url": "https://chibifighters.io", "category": "Game"}, {"name": "CryptoKitties", "description": "Collect and breed digital cats!", "url": "https://cryptokitties.co", "category": "Game"}, {"name": "BTU Hotel", "description": "BTU Hotel is a hotel booking Dapp takes 0% commission. Dapp user earns 100% of the hotel commission directly in crypto into their preferred browser wallet", "url": "https://btu-hotel.com", "category": "Travel"}, - {"name": "Bidali", "description": "Buy from top brands with crypto", "url": "https://commerce.bidali.com/dapp", "category": "Marketplace"}, {"name": "Zerion", "description": "Trade and manage your digital assets across different wallets in one interface", "url": "https://zerion.io", "category": "Finance"}, {"name": "ENS domain manager", "description": "Manage ENS domains", "url": "https://manager.ens.domains", "category": "Tool"}, - {"name": "Humanity", "description": "Human Identity on Ethereum", "url": "https://humanitydao.org", "category": "Social Media"}, {"name": "DEX.AG", "description": "Trade cryptoassets at the best price", "url": "https://dex.ag", "category": "Exchange"}, {"name": "Totle Swap", "description": "Totle automatically finds and acquires the best price across decentralized exchanges for ERC-20 swaps", "url": "https://swap.totle.com", "category": "Exchange"}, {"name": "ATS Bridge", "description": "ATS/ATS20 bridge for self transfers of ATS to ATS20", "url": "https://bridge.artis.network/", "category": "Tool"}, diff --git a/app/src/main/java/com/alphawallet/app/entity/StandardFunctionInterface.java b/app/src/main/java/com/alphawallet/app/entity/StandardFunctionInterface.java index fc9bb45ec5..68fb9eb15b 100644 --- a/app/src/main/java/com/alphawallet/app/entity/StandardFunctionInterface.java +++ b/app/src/main/java/com/alphawallet/app/entity/StandardFunctionInterface.java @@ -28,4 +28,6 @@ default void handleTokenScriptFunction(String function, List selecti default void showWaitSpinner(boolean show) { } default void handleFunctionDenied(String denialMessage) { } + + default void completeFunctionSetup() { } } diff --git a/app/src/main/java/com/alphawallet/app/entity/TSAttrCallback.java b/app/src/main/java/com/alphawallet/app/entity/TSAttrCallback.java new file mode 100644 index 0000000000..3da0ab460c --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/entity/TSAttrCallback.java @@ -0,0 +1,10 @@ +package com.alphawallet.app.entity; + +import com.alphawallet.token.entity.TokenScriptResult; + +import java.util.List; + +public interface TSAttrCallback +{ + void showTSAttributes(List attrs, boolean updateRequired); +} diff --git a/app/src/main/java/com/alphawallet/app/entity/UpdateType.java b/app/src/main/java/com/alphawallet/app/entity/UpdateType.java new file mode 100644 index 0000000000..ee613771cb --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/entity/UpdateType.java @@ -0,0 +1,8 @@ +package com.alphawallet.app.entity; + +public enum UpdateType +{ + USE_CACHE, + UPDATE_IF_REQUIRED, + ALWAYS_UPDATE +} diff --git a/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenscriptFunction.java b/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenscriptFunction.java index 52742d8946..a33568094b 100644 --- a/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenscriptFunction.java +++ b/app/src/main/java/com/alphawallet/app/entity/tokenscript/TokenscriptFunction.java @@ -5,6 +5,7 @@ import android.text.TextUtils; +import com.alphawallet.app.entity.UpdateType; import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.repository.TokenRepository; import com.alphawallet.app.util.BalanceUtils; @@ -730,6 +731,17 @@ public TokenScriptResult.Attribute parseFunctionResult(TransactionResult transac if (!TextUtils.isEmpty(res) && res.equalsIgnoreCase("TRUE")) val = BigInteger.ONE; else val = BigInteger.ZERO; } + else if (attr.syntax == TokenDefinition.Syntax.Integer) + { + if (transactionResult.result.startsWith("0x")) + { + val = new BigInteger(transactionResult.result, 16); + } + else + { + val = new BigInteger(transactionResult.result); + } + } else if (attr.syntax == TokenDefinition.Syntax.NumericString && attr.as != As.Address) { if (transactionResult.result == null) @@ -863,7 +875,8 @@ else if (!TextUtils.isEmpty(element.value)) } else { - return fetchAttrResult(token, attr, tokenId, definition, attrIf, ViewType.VIEW).blockingGet().text; + return fetchAttrResult(token, attr, tokenId, definition, attrIf, + ViewType.VIEW, UpdateType.ALWAYS_UPDATE).blockingGet().text; } return null; @@ -892,14 +905,16 @@ else if (!TextUtils.isEmpty(element.value)) */ public Single fetchAttrResult(Token token, Attribute attr, BigInteger tokenId, - TokenDefinition td, AttributeInterface attrIf, ViewType itemView) + TokenDefinition td, AttributeInterface attrIf, + ViewType itemView, UpdateType update) { - final BigInteger useTokenId = (attr == null) ? BigInteger.ZERO : tokenId; if (attr == null) { return Single.fromCallable(() -> new TokenScriptResult.Attribute("bd", "bd", BigInteger.ZERO, "")); } - else if (token.getAttributeResult(attr.name, useTokenId) != null) + + final BigInteger useTokenId = attr.usesTokenId() ? tokenId : BigInteger.ZERO; + if (token.getAttributeResult(attr.name, useTokenId) != null) { return Single.fromCallable(() -> token.getAttributeResult(attr.name, useTokenId)); } @@ -929,7 +944,10 @@ else if (attr.function == null) // static attribute from tokenId (eg city mappi ContractAddress useAddress = new ContractAddress(attr.function); //always use the function attribute's address long lastTxUpdate = attrIf.getLastTokenUpdate(useAddress.chainId, useAddress.address); TransactionResult cachedResult = attrIf.getFunctionResult(useAddress, attr, useTokenId); //Needs to allow for multiple tokenIds - if ((itemView == ViewType.ITEM_VIEW || (!attr.isVolatile() && ((attrIf.resolveOptimisedAttr(useAddress, attr, cachedResult) || !cachedResult.needsUpdating(lastTxUpdate)))))) //can we use wallet's known data or cached value? + boolean shouldUseCache = checkUpdateRequired(attrIf, attr, cachedResult, update, + itemView == ViewType.ITEM_VIEW, lastTxUpdate, useAddress); + + if (shouldUseCache) //can we use wallet's known data or cached value? { return resultFromDatabase(cachedResult, attr); } @@ -945,6 +963,27 @@ else if (attr.function == null) // static attribute from tokenId (eg city mappi } } + private boolean checkUpdateRequired(AttributeInterface attrIf, Attribute attr, + TransactionResult cachedResult, UpdateType update, + boolean isItemView, long lastTxUpdate, + ContractAddress useAddress) + { + switch (update) + { + case USE_CACHE -> { + return isItemView || !(cachedResult.resultTime == 0); //only update if no result + } + case UPDATE_IF_REQUIRED -> { + return (isItemView || (!attr.isVolatile() && ((attrIf.resolveOptimisedAttr(useAddress, attr, cachedResult) || !cachedResult.needsUpdating(lastTxUpdate))))); + } + case ALWAYS_UPDATE -> { + return isItemView; + } + } + + return true; + } + private Single getEventResult(TransactionResult txResult, Attribute attr, BigInteger tokenId, AttributeInterface attrIf) { //fetch the function @@ -973,7 +1012,12 @@ private Single staticAttribute(Attribute attr, BigI return Single.fromCallable(() -> { try { - if (attr.userInput) + if (refTags.containsKey(attr.name)) + { + attr.userInput = false; + return new TokenScriptResult.Attribute(attr.name, attr.label, BigInteger.ZERO, refTags.get(attr.name), false); + } + else if (attr.userInput) { return new TokenScriptResult.Attribute(attr.name, attr.label, BigInteger.ZERO, "", true); } diff --git a/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java b/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java index 7c6ffa00b5..3847d5f70e 100644 --- a/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java +++ b/app/src/main/java/com/alphawallet/app/service/AssetDefinitionService.java @@ -28,6 +28,7 @@ import com.alphawallet.app.entity.FragmentMessenger; import com.alphawallet.app.entity.QueryResponse; import com.alphawallet.app.entity.TokenLocator; +import com.alphawallet.app.entity.UpdateType; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.nftassets.NFTAsset; import com.alphawallet.app.entity.tokens.Attestation; @@ -592,7 +593,7 @@ public TokenScriptResult.Attribute fetchAttrResult(ContractAddress origin, Attri if (originToken == null || td == null) return null; //produce result - return tokenscriptUtility.fetchAttrResult(originToken, attr, tokenId, td, this, ViewType.VIEW).blockingGet(); + return tokenscriptUtility.fetchAttrResult(originToken, attr, tokenId, td, this, ViewType.VIEW, UpdateType.UPDATE_IF_REQUIRED).blockingGet(); } /** @@ -1395,7 +1396,8 @@ private boolean matchesExistingScript(Token token, String uri) .equalTo("instanceKey", entryKey) .findFirst(); - if (entry != null && !TextUtils.isEmpty(entry.getFileHash()) + if (entry != null && Utils.isIPFS(entry.getIpfsPath()) + && !TextUtils.isEmpty(entry.getFileHash()) && !TextUtils.isEmpty(entry.getFilePath()) && !TextUtils.isEmpty(entry.getIpfsPath()) && entry.getIpfsPath().equals(uri) @@ -2304,7 +2306,8 @@ private List getLocalAttributes(TokenDefinition td, List acti * @param token * @return map of unique tokenIds to lists of allowed functions for that ID - note that we allow the function to be displayed if it has a denial message */ - public Single>> fetchFunctionMap(Token token, @NotNull List tokenIds, ContractType type) + public Single>> fetchFunctionMap(Token token, @NotNull List tokenIds, + ContractType type, UpdateType update) { return Single.fromCallable(() -> { ActionModifier requiredActionModifier = type == ContractType.ATTESTATION ? ActionModifier.ATTESTATION : ActionModifier.NONE; @@ -2316,7 +2319,7 @@ public Single>> fetchFunctionMap(Token token, @NotN //first gather all attrs required - do this so if there's multiple actions using the same attribute for a tokenId we aren't fetching the value repeatedly List requiredAttrNames = getRequiredAttributeNames(actions, td); Map> attrResults // Map of attribute results vs tokenId - = getRequiredAttributeResults(requiredAttrNames, tokenIds, td, token); // Map of all required attribute values vs all the tokenIds + = getRequiredAttributeResults(requiredAttrNames, tokenIds, td, token, update); // Map of all required attribute values vs all the tokenIds for (BigInteger tokenId : tokenIds) { @@ -2387,7 +2390,7 @@ public String checkFunctionDenied(Token token, String actionName, List getAttributeResultsForTokenIds( return results; } - private Map> getRequiredAttributeResults(List requiredAttrNames, List tokenIds, TokenDefinition td, Token token) + private Map> getRequiredAttributeResults(List requiredAttrNames, List tokenIds, + TokenDefinition td, Token token, UpdateType update) { Map> resultSet = new HashMap<>(); for (BigInteger tokenId : tokenIds) @@ -2434,7 +2438,7 @@ private Map> getRequiredAtt Attribute attr = td.attributes.get(attrName); if (attr == null) continue; BigInteger useTokenId = td.useZeroForTokenIdAgnostic(attrName, tokenId); - TokenScriptResult.Attribute attrResult = tokenscriptUtility.fetchAttrResult(token, attr, useTokenId, td, this, ViewType.VIEW).blockingGet(); + TokenScriptResult.Attribute attrResult = tokenscriptUtility.fetchAttrResult(token, attr, useTokenId, td, this, ViewType.VIEW, update).blockingGet(); if (attrResult != null) { Map tokenIdMap = resultSet.get(useTokenId); @@ -2812,7 +2816,8 @@ public void clearResultMap() tokenscriptUtility.clearParseMaps(); } - public Observable resolveAttrs(Token token, TokenDefinition td, BigInteger tokenId, List extraAttrs, ViewType itemView) + public Observable resolveAttrs(Token token, TokenDefinition td, BigInteger tokenId, + List extraAttrs, ViewType itemView, UpdateType update) { TokenDefinition definition = td != null ? td : getAssetDefinition(token); ContractAddress cAddr = new ContractAddress(token.tokenInfo.chainId, token.tokenInfo.address); @@ -2826,7 +2831,7 @@ public Observable resolveAttrs(Token token, TokenDe List attrList = new ArrayList<>(definition.attributes.values()); if (extraAttrs != null) attrList.addAll(extraAttrs); - return resolveAttrs(token, tokenId, definition, attrList, itemView); + return resolveAttrs(token, tokenId, definition, attrList, itemView, update); } public List getAttestationAttrs(Token token, TSAction action, String attnId) @@ -2952,15 +2957,15 @@ private List getRealmItemsForUpdate(Realm realm, TokenDefiniti } private Observable resolveAttrs(Token token, BigInteger tokenId, TokenDefinition td, - List attrList, ViewType itemView) + List attrList, ViewType itemView, UpdateType update) { tokenscriptUtility.buildAttrMap(attrList); return Observable.fromIterable(attrList) .flatMap(attr -> tokenscriptUtility.fetchAttrResult(token, attr, tokenId, - td, this, itemView).toObservable()); + td, this, itemView, update).toObservable()); } - public Observable resolveAttrs(Token token, List tokenIds, List extraAttrs) + public Observable resolveAttrs(Token token, List tokenIds, List extraAttrs, UpdateType update) { TokenDefinition definition = getAssetDefinition(token); if (definition == null) @@ -2975,7 +2980,7 @@ public Observable resolveAttrs(Token token, List tokenIds) diff --git a/app/src/main/java/com/alphawallet/app/ui/AdvancedSettingsActivity.java b/app/src/main/java/com/alphawallet/app/ui/AdvancedSettingsActivity.java index 1c1bd7cd2b..d1ba9cf700 100644 --- a/app/src/main/java/com/alphawallet/app/ui/AdvancedSettingsActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/AdvancedSettingsActivity.java @@ -6,6 +6,8 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; +import android.webkit.CookieManager; +import android.webkit.WebStorage; import android.webkit.WebView; import android.widget.LinearLayout; import android.widget.Toast; @@ -201,12 +203,17 @@ private void onConsoleClicked() private void onClearBrowserCacheClicked() { - WebView webView = new WebView(this); - webView.clearCache(true); - viewModel.blankFilterSettings(); - Single.fromCallable(() -> { + WebView webView = new WebView(this); + webView.clearCache(true); + webView.clearFormData(); + webView.clearHistory(); + webView.clearSslPreferences(); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.removeAllCookies(null); + WebStorage.getInstance().deleteAllData(); + viewModel.blankFilterSettings(); Glide.get(this).clearDiskCache(); return 1; }).subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/com/alphawallet/app/ui/FunctionActivity.java b/app/src/main/java/com/alphawallet/app/ui/FunctionActivity.java index 12cda501c4..5114cd4629 100644 --- a/app/src/main/java/com/alphawallet/app/ui/FunctionActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/FunctionActivity.java @@ -32,6 +32,7 @@ import com.alphawallet.app.entity.SignAuthenticationCallback; import com.alphawallet.app.entity.StandardFunctionInterface; import com.alphawallet.app.entity.TransactionReturn; +import com.alphawallet.app.entity.UpdateType; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.WalletType; import com.alphawallet.app.entity.nftassets.NFTAsset; @@ -65,15 +66,14 @@ import com.alphawallet.token.entity.TSAction; import com.alphawallet.token.entity.TokenScriptResult; import com.alphawallet.token.entity.TokenscriptElement; -import org.web3j.utils.Numeric; import org.web3j.crypto.Hash; import org.web3j.crypto.Keys; import org.web3j.crypto.Sign; +import org.web3j.utils.Numeric; import java.math.BigInteger; import java.nio.charset.StandardCharsets; -import java.security.SignatureException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -220,7 +220,7 @@ private void getAttrs() //Add attestation attributes attrs.append(viewModel.addAttestationAttrs(asset, token, action)); - viewModel.getAssetDefinitionService().resolveAttrs(token, tokenIds, localAttrs) + viewModel.getAssetDefinitionService().resolveAttrs(token, tokenIds, localAttrs, UpdateType.USE_CACHE) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::onAttr, this::onError, () -> displayFunction(attrs.toString())) @@ -393,7 +393,7 @@ public void calculationCompleted(String value, String result, TokenscriptElement @Override public void unresolvedSymbolError(String value) { - Timber.d("ATTR/FA: Resolve: ERROR: " + value); + Timber.d("ATTR/FA: Resolve: ERROR: %s", value); tokenScriptError(value, null); } }; @@ -1003,13 +1003,4 @@ private void getValueFromInnerHTML(CalcJsValueCallback callback, String value, T } }); } - - private void repopulateInputField(String key, String value) - { - tokenView.evaluateJavascript( - "(function() { document.getElementById(\"" + key + "\").innerHTML = \"" + value + "\"; })();", - html -> { - Timber.d("Worked?"); - }); - } } diff --git a/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java b/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java index 09d8cf3849..4920746526 100644 --- a/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java @@ -16,7 +16,6 @@ import android.view.ViewGroup; import android.view.animation.Animation; import android.view.animation.AnimationUtils; -import android.webkit.WebView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -35,6 +34,7 @@ import com.alphawallet.app.entity.GasEstimate; import com.alphawallet.app.entity.SignAuthenticationCallback; import com.alphawallet.app.entity.StandardFunctionInterface; +import com.alphawallet.app.entity.TSAttrCallback; import com.alphawallet.app.entity.TransactionReturn; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.WalletType; @@ -62,7 +62,6 @@ import com.alphawallet.token.entity.TSAction; import com.alphawallet.token.entity.TicketRange; import com.alphawallet.token.entity.TokenScriptResult; -import com.alphawallet.token.entity.TokenScriptResult.Attribute; import com.alphawallet.token.entity.ViewType; import com.alphawallet.token.entity.XMLDsigDescriptor; import com.alphawallet.token.tools.TokenDefinition; @@ -75,11 +74,7 @@ import java.util.Map; import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; -import io.reactivex.schedulers.Schedulers; -import timber.log.Timber; @AndroidEntryPoint public class NFTAssetDetailActivity extends BaseActivity implements StandardFunctionInterface, ActionSheetCallback @@ -317,7 +312,7 @@ private void setup() else { viewModel.getAsset(token, tokenId); - viewModel.updateLocalAttributes(token, tokenId); + viewModel.updateLocalAttributes(token, tokenId); //when complete calls displayTokenView } } @@ -334,7 +329,7 @@ private void initViewModel() viewModel.sig().observe(this, this::onSignature); viewModel.newScriptFound().observe(this, this::newScriptFound); viewModel.walletUpdate().observe(this, this::setupFunctionBar); - viewModel.attrFetchComplete().observe(this, this::displayTokenView); + viewModel.attrFetchComplete().observe(this, this::displayTokenView); //local attr fetch } private void newScriptFound(TokenDefinition td) @@ -412,18 +407,17 @@ private void completeAttestationTokenScriptSetup(TSAction action) } } - private void completeTokenScriptSetup() + private void completeTokenScriptSetup(String prevResult) { - final List attrs = new ArrayList<>(); - - if (viewModel.hasTokenScript(token)) - { - viewModel.getAssetDefinitionService().resolveAttrs(token, new ArrayList<>(Collections.singleton(tokenId)), null) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(attrs::add, this::onError, () -> showTSAttributes(attrs)) - .isDisposed(); - } + viewModel.completeTokenScriptSetup(token, tokenId, prevResult, (attrs, needsUpdate) -> { + //should have resolved all the attrs + tsAttributeLayout.bindTSAttributes(attrs); + //require refresh of TS View + if (needsUpdate) + { + displayTokenView(viewModel.getAssetDefinitionService().getAssetDefinition(token)); + } + }); } private void reloadMetadata() @@ -503,22 +497,9 @@ private void loadAssetFromMetadata(NFTAsset loadedAsset) clearRefreshAnimation(); loadFromOpenSeaData(loadedAsset.getOpenSeaAsset()); - - completeTokenScriptSetup(); } } - private void showTSAttributes(List attrs) - { - //should have resolved all the attrs - tsAttributeLayout.bindTSAttributes(attrs); - } - - private void onError(Throwable throwable) - { - Timber.w(throwable); - } - private void updateTokenImage(NFTAsset asset) { if (triggeredReload) tokenImage.clearImage(); @@ -626,7 +607,7 @@ private void loadFromOpenSeaData(OpenSeaAsset openSeaAsset) private void setupAttestation(TokenDefinition td) { NFTAsset attnAsset = new NFTAsset(); - if (token.getInterfaceSpec() != ContractType.ATTESTATION) + if (token == null || token.getInterfaceSpec() != ContractType.ATTESTATION) { return; } @@ -634,7 +615,7 @@ else if (td != null) { attnAsset.setupScriptElements(td); attnAsset.setupScriptAttributes(td, token); - if (!displayTokenView(td)) + if (!displayTokenView(td)) //display token for Attribute { tokenImage.setupTokenImage(attnAsset); } @@ -796,7 +777,7 @@ private void showIssuer(String issuer) if (!TextUtils.isEmpty(issuer)) { ((TokenInfoView)findViewById(R.id.key_address)).setCopyableValue(issuer); - ((TokenInfoView)findViewById(R.id.key_address)).setVisibility(View.VISIBLE); + findViewById(R.id.key_address).setVisibility(View.VISIBLE); } } @@ -848,6 +829,13 @@ public WalletType getWalletType() return viewModel.getWallet().type; } + @Override + public void completeFunctionSetup() + { + //check if TS needs to be refreshed + completeTokenScriptSetup(tokenScriptView.getAttrResults()); + } + /*** * TokenScript view handling */ @@ -859,6 +847,7 @@ private boolean displayTokenView(final TokenDefinition td) LinearLayout webWrapper = findViewById(R.id.layout_webwrapper); tokenScriptView = new Web3TokenView(this); tokenScriptView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + tokenScriptView.clearCache(true); if (tokenScriptView.renderTokenScriptView(token, new TicketRange(tokenId, token.getAddress()), viewModel.getAssetDefinitionService(), ViewType.VIEW, td)) { diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java index 61d095d887..c01a6909dc 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java @@ -21,8 +21,10 @@ import com.alphawallet.app.entity.GasEstimate; import com.alphawallet.app.entity.Operation; import com.alphawallet.app.entity.SignAuthenticationCallback; +import com.alphawallet.app.entity.TSAttrCallback; import com.alphawallet.app.entity.Transaction; import com.alphawallet.app.entity.TransactionReturn; +import com.alphawallet.app.entity.UpdateType; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.WalletType; import com.alphawallet.app.entity.nftassets.NFTAsset; @@ -133,6 +135,9 @@ public class TokenFunctionViewModel extends BaseViewModel implements Transaction @Nullable private Disposable scriptUpdate; + @Nullable + private Disposable attrRefresh; + @Inject TokenFunctionViewModel( AssetDefinitionService assetDefinitionService, @@ -575,6 +580,10 @@ public void onDestroy() { scriptUpdate.dispose(); } + if (attrRefresh != null && !attrRefresh.isDisposed()) + { + attrRefresh.dispose(); + } gasService.stopGasPriceCycle(); } @@ -852,6 +861,25 @@ private void onCollection(Token token, BigInteger tokenId, NFTAsset asset, OpenS nftAsset.postValue(asset); } + public void completeTokenScriptSetup(Token token, BigInteger tokenId, String prevResult, TSAttrCallback tsCb) + { + final StringBuilder attrsTxt = new StringBuilder(); + final List attrs = new ArrayList<>(); + + if (hasTokenScript(token)) + { + attrRefresh = assetDefinitionService.resolveAttrs(token, new ArrayList<>(Collections.singleton(tokenId)), null, UpdateType.USE_CACHE) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(attr -> { attrs.add(attr); attrsTxt.append(attr.text); }, this::onError, () -> checkUpdatedAttrs(prevResult, attrsTxt, attrs, tsCb)); + } + } + + private void checkUpdatedAttrs(String prevResult, final StringBuilder attrsText, List attrs, TSAttrCallback tsCb) + { + tsCb.showTSAttributes(attrs, !prevResult.equals(attrsText.toString())); + } + private void onAssetError(Throwable throwable) { Timber.d(throwable); @@ -910,13 +938,14 @@ public boolean hasTokenScript(Token token) public void updateLocalAttributes(Token token, BigInteger tokenId) { //Fetch Allowed attributes, then call updateAllowedAttributes - assetDefinitionService.fetchFunctionMap(token, Collections.singletonList(tokenId), token.getInterfaceSpec()) + assetDefinitionService.fetchFunctionMap(token, Collections.singletonList(tokenId), token.getInterfaceSpec(), UpdateType.USE_CACHE) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(availableActions -> updateAllowedAttrs(token, availableActions), this::onError) .isDisposed(); } + /** @noinspection SimplifyOptionalCallChains*/ private void updateAllowedAttrs(Token token, Map> availableActions) { if (!availableActions.keySet().stream().findFirst().isPresent()) @@ -986,7 +1015,6 @@ public Single findWallet(String walletAddress) return genericWalletInteract.findWallet(walletAddress); } - public String addAttestationAttrs(NFTAsset asset, Token token, TSAction action) { StringBuilder attrs = new StringBuilder(); diff --git a/app/src/main/java/com/alphawallet/app/web3/Web3TokenView.java b/app/src/main/java/com/alphawallet/app/web3/Web3TokenView.java index 5aea50ac18..0b8b22010e 100644 --- a/app/src/main/java/com/alphawallet/app/web3/Web3TokenView.java +++ b/app/src/main/java/com/alphawallet/app/web3/Web3TokenView.java @@ -1,15 +1,11 @@ package com.alphawallet.app.web3; -import static androidx.webkit.WebSettingsCompat.FORCE_DARK_OFF; -import static androidx.webkit.WebSettingsCompat.FORCE_DARK_ON; -import static androidx.webkit.WebSettingsCompat.setAlgorithmicDarkeningAllowed; import static com.alphawallet.app.service.AssetDefinitionService.ASSET_DETAIL_VIEW_NAME; import static com.alphawallet.app.service.AssetDefinitionService.ASSET_SUMMARY_VIEW_NAME; import static com.alphawallet.token.tools.TokenDefinition.TOKENSCRIPT_ERROR; import android.annotation.SuppressLint; import android.content.Context; -import android.content.res.Configuration; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Base64; @@ -17,10 +13,8 @@ import android.view.View; import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; -import android.webkit.JsPromptResult; import android.webkit.JsResult; import android.webkit.WebChromeClient; -import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; @@ -35,6 +29,8 @@ import com.alphawallet.app.BuildConfig; import com.alphawallet.app.R; +import com.alphawallet.app.entity.StandardFunctionInterface; +import com.alphawallet.app.entity.UpdateType; import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.entity.tokenscript.TokenScriptRenderCallback; import com.alphawallet.app.entity.tokenscript.WebCompletionCallback; @@ -63,6 +59,7 @@ import java.util.Objects; import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import io.realm.Realm; import io.realm.RealmResults; @@ -90,12 +87,16 @@ public class Web3TokenView extends WebView private RealmResults realmAuxUpdates; protected WebCompletionCallback keyPressCallback; + @Nullable + private Disposable buildViewAttrs; @Nullable private OnSignPersonalMessageListener onSignPersonalMessageListener; @Nullable private OnSetValuesListener onSetValuesListener; + private String attrResults; + public Web3TokenView(@NonNull Context context) { super(context); init(); @@ -456,7 +457,8 @@ private void showLegacyView(Token token, TicketRange range) loadData(displayData, "text/html", "utf-8"); } - public boolean renderTokenScriptView(Token token, TicketRange range, AssetDefinitionService assetService, ViewType itemView, final TokenDefinition td) + public boolean renderTokenScriptView(Token token, TicketRange range, AssetDefinitionService assetService, ViewType itemView, + final TokenDefinition td) { BigInteger tokenId = range.tokenIds.get(0); if (!td.hasTokenView()) @@ -464,14 +466,15 @@ public boolean renderTokenScriptView(Token token, TicketRange range, AssetDefini return false; } + attrResults = ""; + final StringBuilder attrs = assetService.getTokenAttrs(token, tokenId, range.tokenIds.size()); - assetService.resolveAttrs(token, null, tokenId, assetService.getTokenViewLocalAttributes(token), itemView) + buildViewAttrs = assetService.resolveAttrs(token, null, tokenId, assetService.getTokenViewLocalAttributes(token), itemView, UpdateType.USE_CACHE) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(attr -> onAttr(attr, attrs), throwable -> onError(token, throwable, range), - () -> displayTicket(token, assetService, attrs, itemView, range, td)) - .isDisposed(); + () -> displayTokenView(token, assetService, attrs, itemView, range, td)); return true; } @@ -484,7 +487,7 @@ public boolean renderTokenScriptView(Token token, TicketRange range, AssetDefini * @param iconified * @param range */ - private void displayTicket(Token token, AssetDefinitionService assetService, StringBuilder attrs, ViewType iconified, TicketRange range, final TokenDefinition td) + private void displayTokenView(Token token, AssetDefinitionService assetService, StringBuilder attrs, ViewType iconified, TicketRange range, final TokenDefinition td) { setVisibility(View.VISIBLE); String viewName; @@ -540,6 +543,11 @@ private long getLastUpdateTime(Realm realm, Token token, BigInteger tokenId) return lastResultTime + 1; } + public String getAttrResults() + { + return attrResults; + } + @Override public void onPause() { @@ -569,6 +577,11 @@ public void destroy() realmAuxUpdates.getRealm().close(); } } + + if (buildViewAttrs != null && !buildViewAttrs.isDisposed()) + { + buildViewAttrs.dispose(); + } } /** @@ -606,5 +619,6 @@ private void onError(Token token, Throwable throwable, TicketRange range) private void onAttr(TokenScriptResult.Attribute attribute, StringBuilder attrs) { TokenScriptResult.addPair(attrs, attribute.id, attribute.text); + attrResults += attribute.text; } } diff --git a/app/src/main/java/com/alphawallet/app/widget/FunctionButtonBar.java b/app/src/main/java/com/alphawallet/app/widget/FunctionButtonBar.java index 6950134e31..fd4cfc3e93 100644 --- a/app/src/main/java/com/alphawallet/app/widget/FunctionButtonBar.java +++ b/app/src/main/java/com/alphawallet/app/widget/FunctionButtonBar.java @@ -39,6 +39,7 @@ import com.alphawallet.app.entity.ItemClick; import com.alphawallet.app.entity.OnRampContract; import com.alphawallet.app.entity.StandardFunctionInterface; +import com.alphawallet.app.entity.UpdateType; import com.alphawallet.app.entity.WalletType; import com.alphawallet.app.entity.tokens.Attestation; import com.alphawallet.app.entity.tokens.Token; @@ -698,7 +699,7 @@ private void getFunctionMap(AssetDefinitionService assetSvs, ContractType type) findViewById(R.id.wait_buttons).setVisibility(View.VISIBLE); //get the available map for this collection - assetSvs.fetchFunctionMap(token, selection, type) + assetSvs.fetchFunctionMap(token, selection, type, UpdateType.UPDATE_IF_REQUIRED) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(availabilityMap -> setupTokenMap(token, availabilityMap), this::onMapFetchError) @@ -728,6 +729,8 @@ private void setupTokenMap(@NotNull Token token, Map> a populateButtons(token, tokenId); showButtons(); } + + callStandardFunctions.completeFunctionSetup(); } private void showButtons() diff --git a/app/src/main/java/com/alphawallet/app/widget/NFTImageView.java b/app/src/main/java/com/alphawallet/app/widget/NFTImageView.java index b94af0b424..0c5656d223 100644 --- a/app/src/main/java/com/alphawallet/app/widget/NFTImageView.java +++ b/app/src/main/java/com/alphawallet/app/widget/NFTImageView.java @@ -212,7 +212,6 @@ private void loadImage(String url, String backgroundColor) throws IllegalArgumen private void setWebView(String imageUrl, ImageType hint) { progressBar.setVisibility(VISIBLE); - webView.setOnTouchListener(this); webView.setVerticalScrollBarEnabled(false); webView.setHorizontalScrollBarEnabled(false); webView.setWebChromeClient(new WebChromeClient()); @@ -245,6 +244,7 @@ public void onPageFinished(WebView view, String url) else if (useType.getImageType() == ImageType.ANIM) { String loaderAnim = loadFile(getContext(), R.raw.token_anim).replace("[URL]", imageUrl).replace("[MIME]", useType.getMimeType()); + webView.setOnTouchListener(this); webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true); webView.getSettings().setMediaPlaybackRequiresUserGesture(false); @@ -256,6 +256,7 @@ else if (useType.getImageType() == ImageType.ANIM) } else if (useType.getImageType() == ImageType.MODEL) { + webView.setOnTouchListener(this); String loader = loadFile(getContext(), R.raw.token_model).replace("[URL]", imageUrl); String base64 = android.util.Base64.encodeToString(loader.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT); webView.loadData(base64, "text/html; charset=utf-8", "base64"); @@ -554,7 +555,11 @@ public DisplayType(String url, ImageType hint) { case "": mimeStr = ""; - if (hint == ImageType.IMAGE || hint == ImageType.ANIM) + if (url.contains("tokenscript.org")) + { + type = ImageType.WEB; + } + else if (hint == ImageType.IMAGE || hint == ImageType.ANIM) { type = hint; } diff --git a/lib/src/main/java/com/alphawallet/token/entity/TransactionResult.java b/lib/src/main/java/com/alphawallet/token/entity/TransactionResult.java index 81021dabff..a00298878e 100644 --- a/lib/src/main/java/com/alphawallet/token/entity/TransactionResult.java +++ b/lib/src/main/java/com/alphawallet/token/entity/TransactionResult.java @@ -32,6 +32,6 @@ public TransactionResult(long chainId, String address, BigInteger tokenId, Attri public boolean needsUpdating(long lastTxTime) { //if contract had new transactions then update, or if last tx was -1 (always check) - return (resultTime == 0 || ((System.currentTimeMillis() + 5*10*1000) < resultTime) || lastTxTime < 0 || lastTxTime > resultTime); + return resultTime == 0 || System.currentTimeMillis() > (resultTime + 30*1000) || lastTxTime < 0 || lastTxTime > resultTime; } }