From f60184afaa69b47ac1a67314a40fc8ce68cd7adb Mon Sep 17 00:00:00 2001 From: micwallace Date: Tue, 20 Feb 2024 11:38:54 +1100 Subject: [PATCH] Feat: tokenscript engine js (#3346) * feat: initial embedded viewer PoC * feat: Move embedded viewer to a new activity This is needed to prevent scrolling issues * feat: Complete transaction signing * feat: Various cleanup & fixes * chore: remove redundant and outdated stuff * chore: remove redundant and outdated stuff * feat: add schema detection to enable embedded view * fix: various fixes for chain ID * fix: various fixes for chain ID * feat: add refresh button * chore: update viewer URL * Rebase and update * Fix for token send and tidy up code * fix integration test --------- Co-authored-by: James Brown --- .../app/AnalyticsSettingsTest.java | 6 +- app/src/main/AndroidManifest.xml | 5 + .../repository/PreferenceRepositoryType.java | 4 + .../SharedPreferenceRepository.java | 16 +- .../app/ui/AdvancedSettingsActivity.java | 148 ++-- .../app/ui/DappBrowserFragment.java | 2 +- .../app/ui/NFTAssetDetailActivity.java | 46 +- .../app/ui/TokenScriptJsActivity.java | 773 ++++++++++++++++++ .../app/ui/TransferNFTActivity.java | 19 +- .../ui/widget/entity/NFTAttributeLayout.java | 3 +- .../alphawallet/app/util/ShortcutUtils.java | 5 +- .../viewmodel/AdvancedSettingsViewModel.java | 10 + .../app/viewmodel/NFTAssetsViewModel.java | 2 - .../app/viewmodel/TokenFunctionViewModel.java | 14 +- .../com/alphawallet/app/web3/Web3View.java | 8 +- .../app/widget/ActionSheetDialog.java | 5 + .../app/widget/FunctionButtonBar.java | 40 +- app/src/main/res/drawable/ic_tokenscript.xml | 19 + .../res/layout/activity_generic_settings.xml | 23 +- .../res/layout/activity_tokenscript_js.xml | 27 + app/src/main/res/values-es/strings.xml | 1 + app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-id/strings.xml | 1 + app/src/main/res/values-my/strings.xml | 1 + app/src/main/res/values-vi/strings.xml | 1 + app/src/main/res/values-zh/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 27 files changed, 1077 insertions(+), 105 deletions(-) create mode 100644 app/src/main/java/com/alphawallet/app/ui/TokenScriptJsActivity.java create mode 100644 app/src/main/res/drawable/ic_tokenscript.xml create mode 100644 app/src/main/res/layout/activity_tokenscript_js.xml diff --git a/app/src/androidTest/java/com/alphawallet/app/AnalyticsSettingsTest.java b/app/src/androidTest/java/com/alphawallet/app/AnalyticsSettingsTest.java index e27a2d8c7b..948deebdbf 100644 --- a/app/src/androidTest/java/com/alphawallet/app/AnalyticsSettingsTest.java +++ b/app/src/androidTest/java/com/alphawallet/app/AnalyticsSettingsTest.java @@ -9,6 +9,7 @@ import static com.alphawallet.app.steps.Steps.closeSecurityWarning; import static com.alphawallet.app.steps.Steps.createNewWallet; import static com.alphawallet.app.steps.Steps.gotoSettingsPage; +import static com.alphawallet.app.steps.Steps.scrollToImproved; import static com.alphawallet.app.steps.Steps.selectMenu; import static com.alphawallet.app.util.Helper.click; @@ -38,7 +39,10 @@ public void title_should_see_crash_report_settings_page() gotoSettingsPage(); selectMenu("Advanced"); Helper.wait(1); - onView(withId(R.id.layout)).perform(swipeUp()); + onView(withId(R.id.scroll_layer)).perform(swipeUp()); + onView(withId(R.id.scroll_layer)).perform(swipeUp()); + onView(withSubstring("Crash")).perform(scrollToImproved()); + click(withSubstring("Crash")); shouldSee("Share Anonymous Data"); } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 54b2037996..8d9c1a8939 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -326,6 +326,11 @@ android:hardwareAccelerated="true" android:label="ERC1155 Asset Details" /> + + - { - 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()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(v -> - { - Toast.makeText(this, getString(R.string.toast_browser_cache_cleared), Toast.LENGTH_SHORT).show(); - finish(); - }).isDisposed(); + { + 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()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(v -> + { + Toast.makeText(this, getString(R.string.toast_browser_cache_cleared), Toast.LENGTH_SHORT).show(); + finish(); + }).isDisposed(); } private void onReloadTokenDataClicked() @@ -242,9 +256,9 @@ private void onReloadTokenDataClicked() viewModel.stopChainActivity(); showWaitDialog(); clearTokenCache = viewModel.resetTokenData() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::showResetResult); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::showResetResult); viewModel.blankFilterSettings(); }); @@ -340,7 +354,7 @@ private void askWritePermission() private boolean checkWritePermission() { return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - == PackageManager.PERMISSION_GRANTED; + == PackageManager.PERMISSION_GRANTED; } @Override diff --git a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java index 40ca8201d9..080b4eb2eb 100644 --- a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java @@ -820,7 +820,7 @@ private void displayCloseWC() private void setupWeb3(Wallet wallet) { if (wallet == null) { return; } - web3.setChainId(activeNetwork.chainId); + web3.setChainId(activeNetwork.chainId, false); web3.setWalletAddress(new Address(wallet.address)); web3.setWebChromeClient(new WebChromeClient() 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 9998f3838e..56f2ad7474 100644 --- a/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java @@ -114,6 +114,7 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc private boolean triggeredReload; private long chainId; private Web3TokenView tokenScriptView; + private boolean usingNativeTokenScript = false; @Override protected void onCreate(@Nullable Bundle savedInstanceState) @@ -289,7 +290,7 @@ private void handleShortCut(String walletAddress) token = viewModel.getTokensService().getToken(walletAddress, chainId, tokenAddress); if (token == null) { - ShortcutUtils.showConfirmationDialog(this, singletonList(tokenAddress), getString(R.string.remove_shortcut_while_token_not_found)); + ShortcutUtils.showConfirmationDialog(this, singletonList(tokenAddress), getString(R.string.remove_shortcut_while_token_not_found), null); } else { @@ -305,6 +306,12 @@ private void setup() setTitle(token.tokenInfo.name); updateDefaultTokenData(); + if (!viewModel.getUseTSViewer()) + { + TokenDefinition td = viewModel.getAssetDefinitionService().getAssetDefinition(this.token); + this.usingNativeTokenScript = td.nameSpace != null; + } + if (asset != null && asset.isAttestation()) { setupAttestation(viewModel.getAssetDefinitionService().getAssetDefinition(token)); @@ -312,7 +319,10 @@ private void setup() else { viewModel.getAsset(token, tokenId); - viewModel.updateLocalAttributes(token, tokenId); //when complete calls displayTokenView + if (this.usingNativeTokenScript) + { + viewModel.updateLocalAttributes(token, tokenId); //when complete calls displayTokenView + } } } @@ -344,6 +354,11 @@ private void newScriptFound(TokenDefinition td) setTitle(token.getTokenName(viewModel.getAssetDefinitionService(), 1)); + if (!viewModel.getUseTSViewer()) + { + this.usingNativeTokenScript = td.nameSpace != null; + } + //now re-load the verbs if already called. If wallet is null this won't complete setupFunctionBar(viewModel.getWallet()); @@ -351,7 +366,7 @@ private void newScriptFound(TokenDefinition td) { setupAttestation(td); } - else + else if (this.usingNativeTokenScript) { displayTokenView(td); } @@ -389,7 +404,16 @@ private void setupFunctionBar(Wallet wallet) if (token != null && wallet != null && (BuildConfig.DEBUG || wallet.type != WalletType.WATCH)) { FunctionButtonBar functionBar = findViewById(R.id.layoutButtons); - functionBar.setupFunctions(this, viewModel.getAssetDefinitionService(), token, null, Collections.singletonList(tokenId)); + + if (this.usingNativeTokenScript) + { + functionBar.setupFunctions(this, viewModel.getAssetDefinitionService(), token, null, Collections.singletonList(tokenId)); + } + else + { + functionBar.setupFunctionsForJsViewer(this, R.string.title_tokenscript, this.token, Collections.singletonList(tokenId)); + } + functionBar.revealButtons(); functionBar.setWalletType(wallet.type); } @@ -880,4 +904,18 @@ private boolean displayTokenView(final TokenDefinition td) return couldDisplay; } + + public void handleClick(String action, int actionId) { + + if (actionId != R.string.title_tokenscript) + return; + + Intent intent = new Intent(NFTAssetDetailActivity.this, TokenScriptJsActivity.class); + intent.putExtra(C.Key.WALLET, (Wallet) getIntent().getParcelableExtra(C.Key.WALLET)); + intent.putExtra(C.EXTRA_CHAIN_ID, getIntent().getLongExtra(C.EXTRA_CHAIN_ID, chainId)); + intent.putExtra(C.EXTRA_ADDRESS, getIntent().getStringExtra(C.EXTRA_ADDRESS)); + intent.putExtra(C.EXTRA_TOKEN_ID, getIntent().getStringExtra(C.EXTRA_TOKEN_ID)); + if (asset != null) intent.putExtra(C.EXTRA_NFTASSET, (NFTAsset) getIntent().getParcelableExtra(C.EXTRA_NFTASSET)); + startActivity(intent); + } } diff --git a/app/src/main/java/com/alphawallet/app/ui/TokenScriptJsActivity.java b/app/src/main/java/com/alphawallet/app/ui/TokenScriptJsActivity.java new file mode 100644 index 0000000000..6dbefd2298 --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/ui/TokenScriptJsActivity.java @@ -0,0 +1,773 @@ +package com.alphawallet.app.ui; + +import static com.alphawallet.app.widget.AWalletAlertDialog.ERROR; +import static com.alphawallet.app.widget.AWalletAlertDialog.WARNING; +import static org.web3j.protocol.core.methods.request.Transaction.createFunctionCallTransaction; +import static java.util.Collections.singletonList; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Pair; +import android.view.Menu; +import android.view.MenuItem; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.view.menu.ActionMenuItemView; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.ViewModelProvider; + +import com.alphawallet.app.C; +import com.alphawallet.app.R; +import com.alphawallet.app.analytics.Analytics; +import com.alphawallet.app.entity.AnalyticsProperties; +import com.alphawallet.app.entity.GasEstimate; +import com.alphawallet.app.entity.NetworkInfo; +import com.alphawallet.app.entity.SignAuthenticationCallback; +import com.alphawallet.app.entity.StandardFunctionInterface; +import com.alphawallet.app.entity.TransactionReturn; +import com.alphawallet.app.entity.Wallet; +import com.alphawallet.app.entity.WalletType; +import com.alphawallet.app.entity.analytics.ActionSheetSource; +import com.alphawallet.app.entity.nftassets.NFTAsset; +import com.alphawallet.app.entity.tokens.Token; +import com.alphawallet.app.repository.TokenRepository; +import com.alphawallet.app.service.GasService; +import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; +import com.alphawallet.app.util.ShortcutUtils; +import com.alphawallet.app.viewmodel.DappBrowserViewModel; +import com.alphawallet.app.viewmodel.TokenFunctionViewModel; +import com.alphawallet.app.web3.OnEthCallListener; +import com.alphawallet.app.web3.OnSignMessageListener; +import com.alphawallet.app.web3.OnSignPersonalMessageListener; +import com.alphawallet.app.web3.OnSignTransactionListener; +import com.alphawallet.app.web3.OnSignTypedMessageListener; +import com.alphawallet.app.web3.OnWalletActionListener; +import com.alphawallet.app.web3.OnWalletAddEthereumChainObjectListener; +import com.alphawallet.app.web3.Web3View; +import com.alphawallet.app.web3.entity.Address; +import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject; +import com.alphawallet.app.web3.entity.Web3Call; +import com.alphawallet.app.web3.entity.Web3Transaction; +import com.alphawallet.app.widget.AWalletAlertDialog; +import com.alphawallet.app.widget.ActionSheet; +import com.alphawallet.app.widget.ActionSheetDialog; +import com.alphawallet.app.widget.ActionSheetSignDialog; +import com.alphawallet.app.widget.CertifiedToolbarView; +import com.alphawallet.ethereum.EthereumNetworkBase; +import com.alphawallet.hardware.SignatureFromKey; +import com.alphawallet.token.entity.EthereumMessage; +import com.alphawallet.token.entity.EthereumTypedMessage; +import com.alphawallet.token.entity.SignMessageType; +import com.alphawallet.token.entity.Signable; +import com.alphawallet.token.entity.XMLDsigDescriptor; + +import org.jetbrains.annotations.NotNull; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.methods.response.EthCall; +import org.web3j.utils.Numeric; + +import java.math.BigInteger; + +import dagger.hilt.android.AndroidEntryPoint; +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; + +@AndroidEntryPoint +public class TokenScriptJsActivity extends BaseActivity implements StandardFunctionInterface, ActionSheetCallback, + OnSignTransactionListener, OnSignPersonalMessageListener, OnSignTypedMessageListener, OnSignMessageListener, + OnEthCallListener, OnWalletAddEthereumChainObjectListener, OnWalletActionListener +{ + private DappBrowserViewModel viewModel; + private Token token; + private BigInteger tokenId; + private NFTAsset asset; + private String sequenceId; + private ActionSheet confirmationDialog; + private AWalletAlertDialog dialog; + private Animation rotation; + private ActivityResultLauncher handleTransactionSuccess; + private ActivityResultLauncher getGasSettings; + private long chainId; + private Web3View tokenScriptView; + private Wallet wallet; + private NetworkInfo activeNetwork; + private AWalletAlertDialog chainSwapDialog; + private AWalletAlertDialog resultDialog; + private AWalletAlertDialog errorDialog; + private AddEthereumChainPrompt addCustomChainDialog; + private ActionMenuItemView refreshMenu; + + private static String VIEWER_URL = "https://viewer.tokenscript.org"; + //"http://192.168.1.15:3333"; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_tokenscript_js); + + initViews(); + + toolbar(); + + initIntents(); + + initViewModel(); + } + + private void initIntents() + { + handleTransactionSuccess = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + result -> + { + if (result.getData() == null) return; + String transactionHash = result.getData().getStringExtra(C.EXTRA_TXHASH); + //process hash + if (!TextUtils.isEmpty(transactionHash)) + { + Intent intent = new Intent(); + intent.putExtra(C.EXTRA_TXHASH, transactionHash); + setResult(RESULT_OK, intent); + finish(); + } + }); + + getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + result -> confirmationDialog.setCurrentGasIndex(result)); + } + + @Override + public void onResume() + { + super.onResume(); + if (viewModel != null) + { + getIntentData(); + } + else + { + recreate(); + } + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + viewModel.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(@NonNull Menu menu) + { + getMenuInflater().inflate(R.menu.menu_refresh, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + if (item.getItemId() == R.id.action_reload_metadata) + { + tokenScriptView.reload(); + } + return super.onOptionsItemSelected(item); + } + + private void initViews() + { + rotation = AnimationUtils.loadAnimation(this, R.anim.rotate_refresh); + rotation.setRepeatCount(Animation.INFINITE); + } + + private void getIntentData() + { + chainId = getIntent().getLongExtra(C.EXTRA_CHAIN_ID, EthereumNetworkBase.MAINNET_ID); + tokenId = new BigInteger(getIntent().getStringExtra(C.EXTRA_TOKEN_ID)); + asset = getIntent().getParcelableExtra(C.EXTRA_NFTASSET); + sequenceId = getIntent().getStringExtra(C.EXTRA_STATE); + String walletAddress = getWalletFromIntent(); + + if (C.ACTION_TOKEN_SHORTCUT.equals(getIntent().getAction())) + { + handleShortCut(walletAddress); + } + else + { + token = resolveAssetToken(); + setup(); + } + } + + private String getWalletFromIntent() + { + Wallet w = getIntent().getParcelableExtra(C.Key.WALLET); + if (w != null) + { + return w.address; + } + else + { + return getIntent().getStringExtra(C.Key.WALLET); + } + } + + private Token resolveAssetToken() + { + if (asset != null && asset.isAttestation()) + { + return viewModel.getTokenService().getAttestation(chainId, getIntent().getStringExtra(C.EXTRA_ADDRESS), asset.getAttestationID()); + } + else + { + return viewModel.getTokenService().getToken(chainId, getIntent().getStringExtra(C.EXTRA_ADDRESS)); + } + } + + private void handleShortCut(String walletAddress) + { + String tokenAddress = getIntent().getStringExtra(C.EXTRA_ADDRESS); + token = viewModel.getTokenService().getToken(walletAddress, chainId, tokenAddress); + if (token == null) + { + ShortcutUtils.showConfirmationDialog(this, singletonList(tokenAddress), getString(R.string.remove_shortcut_while_token_not_found), null); + } + else + { + asset = token.getAssetForToken(tokenId); + setup(); + } + } + + private void setup() + { + TokenFunctionViewModel tsViewModel = new ViewModelProvider(this) + .get(TokenFunctionViewModel.class); + tsViewModel.checkTokenScriptValidity(token); + setTitle(token.tokenInfo.name); + } + + private void initViewModel() + { + viewModel = new ViewModelProvider(this) + .get(DappBrowserViewModel.class); + viewModel.transactionFinalised().observe(this, this::txWritten); + viewModel.transactionSigned().observe(this, this::txSigned); + viewModel.transactionError().observe(this, this::txError); + viewModel.defaultWallet().observe(this, this::onDefaultWallet); + + TokenFunctionViewModel tsViewModel = new ViewModelProvider(this) + .get(TokenFunctionViewModel.class); + tsViewModel.sig().observe(this, this::onSignature); + + getIntentData(); + + viewModel.setNetwork(chainId); + activeNetwork = viewModel.getNetworkInfo(chainId); + + viewModel.findWallet(); + } + + private void onDefaultWallet(Wallet wallet) + { + this.wallet = wallet; + if (activeNetwork != null && wallet != null) + { + openTokenscriptWebview(wallet); + } + } + + private void onSignature(XMLDsigDescriptor descriptor) + { + CertifiedToolbarView certificateToolbar = findViewById(R.id.certified_toolbar); + certificateToolbar.onSigData(descriptor, this); + } + + private void txWritten(TransactionReturn txData) + { + if (confirmationDialog != null && confirmationDialog.isShowing()) + { + confirmationDialog.transactionWritten(txData.hash); + } + + tokenScriptView.onSignTransactionSuccessful(txData); + } + + private void txSigned(TransactionReturn txData) + { + confirmationDialog.transactionWritten(txData.getDisplayData()); + tokenScriptView.onSignTransactionSuccessful(txData); + } + + //Transaction failed to be sent + private void txError(TransactionReturn rtn) + { + confirmationDialog.dismiss(); + tokenScriptView.onSignCancel(rtn.tx.leafPosition); + + if (resultDialog != null && resultDialog.isShowing()) resultDialog.dismiss(); + resultDialog = new AWalletAlertDialog(this); + resultDialog.setIcon(ERROR); + resultDialog.setTitle(R.string.error_transaction_failed); + resultDialog.setMessage(rtn.throwable.getMessage()); + resultDialog.setButtonText(R.string.button_ok); + resultDialog.setButtonListener(v -> { + resultDialog.dismiss(); + }); + resultDialog.show(); + + if (confirmationDialog != null && confirmationDialog.isShowing()) + confirmationDialog.dismiss(); + } + + private void calculateEstimateDialog() + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setTitle(getString(R.string.calc_gas_limit)); + dialog.setIcon(AWalletAlertDialog.NONE); + dialog.setProgressMode(); + dialog.setCancelable(false); + dialog.show(); + } + + private void estimateError(Pair estimate) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(WARNING); + dialog.setTitle(estimate.first.hasError() ? + R.string.dialog_title_gas_estimation_failed : + R.string.confirm_transaction + ); + String message = estimate.first.hasError() ? + getString(R.string.dialog_message_gas_estimation_failed, estimate.first.getError()) : + getString(R.string.error_transaction_may_fail); + dialog.setMessage(message); + dialog.setButtonText(R.string.action_proceed); + dialog.setSecondaryButtonText(R.string.action_cancel); + dialog.setButtonListener(v -> { + Web3Transaction w3tx = estimate.second; + BigInteger gasEstimate = GasService.getDefaultGasLimit(token, w3tx); + checkConfirm(new Web3Transaction(w3tx.recipient, w3tx.contract, w3tx.value, w3tx.gasPrice, gasEstimate, w3tx.nonce, w3tx.payload, w3tx.description)); + }); + dialog.setSecondaryButtonListener(v -> dialog.dismiss()); + dialog.show(); + } + + private void checkConfirm(Web3Transaction w3tx) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + confirmationDialog = new ActionSheetDialog(this, w3tx, token, "", //TODO: Reverse resolve address + w3tx.recipient.toString(), viewModel.getTokenService(), this); + confirmationDialog.setURL("TokenScript"); + confirmationDialog.setCanceledOnTouchOutside(false); + confirmationDialog.show(); + } + + @Override + public void getAuthorisation(SignAuthenticationCallback callback) + { + viewModel.getAuthorisation(wallet, this, callback); + } + + @Override + public void sendTransaction(Web3Transaction finalTx) + { + viewModel.requestSignature(finalTx, wallet, activeNetwork.chainId); + } + + @Override + public void completeSendTransaction(Web3Transaction tx, SignatureFromKey signature) + { + viewModel.sendTransaction(wallet, activeNetwork.chainId, tx, signature); + } + + @Override + public void signTransaction(Web3Transaction tx) + { + viewModel.requestSignatureOnly(tx, wallet, activeNetwork.chainId); + } + + @Override + public void completeSignTransaction(Web3Transaction w3Tx, SignatureFromKey signature) + { + viewModel.signTransaction(activeNetwork.chainId, w3Tx, signature); + } + + + @Override + public void dismissed(String txHash, long callbackId, boolean actionCompleted) + { + //actionsheet dismissed - if action not completed then user cancelled + if (!actionCompleted) + { + //actionsheet dismissed before completing signing. + tokenScriptView.onSignCancel(callbackId); + } + } + + @Override + public void notifyConfirm(String mode) + { + AnalyticsProperties props = new AnalyticsProperties(); + props.put(Analytics.PROPS_ACTION_SHEET_MODE, mode); + props.put(Analytics.PROPS_ACTION_SHEET_SOURCE, ActionSheetSource.BROWSER); + } + + @Override + public ActivityResultLauncher gasSelectLauncher() + { + return getGasSettings; + } + + @Override + public WalletType getWalletType() + { + return wallet.type; + } + + @Override + public GasService getGasService() + { + return viewModel.getGasService(); + } + + /*** + * TokenScript view handling + */ + private void openTokenscriptWebview(Wallet wallet) + { + try + { + tokenScriptView = findViewById(R.id.web3view); + + tokenScriptView.setWebChromeClient(new WebChromeClient()); + tokenScriptView.setWebViewClient(new WebViewClient(){ + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + refreshMenu = findViewById(R.id.action_reload_metadata); + refreshMenu.startAnimation(rotation); + super.onPageStarted(view, url, favicon); + } + @Override + public void onPageFinished(WebView view, String url) { + if (refreshMenu != null) + { + refreshMenu.clearAnimation(); + } + super.onPageFinished(view, url); + } + }); + + tokenScriptView.getSettings().setSupportMultipleWindows(true); + + tokenScriptView.setWebViewClient(new WebViewClient()); + tokenScriptView.setChainId(activeNetwork.chainId, false); + tokenScriptView.setWalletAddress(new Address(wallet.address)); + + tokenScriptView.setOnSignMessageListener(this); + tokenScriptView.setOnSignPersonalMessageListener(this); + tokenScriptView.setOnSignTransactionListener(this); + tokenScriptView.setOnSignTypedMessageListener(this); + tokenScriptView.setOnEthCallListener(this); + tokenScriptView.setOnWalletAddEthereumChainObjectListener(this); + tokenScriptView.setOnWalletActionListener(this); + + tokenScriptView.resetView(); + tokenScriptView.loadUrl(VIEWER_URL + "/?viewType=alphawallet&chain=" + chainId + "&contract=" + token.tokenInfo.address + "&tokenId=" + tokenId); + } + catch (Exception e) + { + Timber.e(e); + } + } + + + public void onSignMessage(final EthereumMessage message) + { + handleSignMessage(message); + } + + + public void onSignPersonalMessage(final EthereumMessage message) + { + handleSignMessage(message); + } + + + public void onSignTypedMessage(@NotNull EthereumTypedMessage message) + { + if (message.getPrehash() == null || message.getMessageType() == SignMessageType.SIGN_ERROR) + { + tokenScriptView.onSignCancel(message.getCallbackId()); + } + else + { + handleSignMessage(message); + } + } + + + public void onEthCall(Web3Call call) + { + Timber.tag("TOKENSCRIPT").w("Received web3 request: %s", call.payload); + + Single.fromCallable(() -> { + //let's make the call + Web3j web3j = TokenRepository.getWeb3jService(activeNetwork.chainId); + //construct call + org.web3j.protocol.core.methods.request.Transaction transaction + = createFunctionCallTransaction(wallet.address, null, null, call.gasLimit, call.to.toString(), call.value, call.payload); + return web3j.ethCall(transaction, call.blockParam).send(); + }).map(EthCall::getValue) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(result -> tokenScriptView.onCallFunctionSuccessful(call.leafPosition, result), + error -> tokenScriptView.onCallFunctionError(call.leafPosition, error.getMessage())) + .isDisposed(); + } + + @Override + public void onWalletAddEthereumChainObject(long callbackId, WalletAddEthereumChainObject chainObj) + { + // read chain value + long chainId = chainObj.getChainId(); + final NetworkInfo info = viewModel.getNetworkInfo(chainId); + + // handle unknown network + if (info == null) + { + // show add custom chain dialog + addCustomChainDialog = new AddEthereumChainPrompt(this, chainObj, chainObject -> { + if (viewModel.addCustomChain(chainObject)) + { + loadNewNetwork(chainObj.getChainId()); + } + else + { + displayError(R.string.error_invalid_url, 0); + } + addCustomChainDialog.dismiss(); + }); + addCustomChainDialog.show(); + } + else + { + changeChainRequest(callbackId, info); + } + } + + private void loadNewNetwork(long newNetworkId) + { + if (activeNetwork == null || activeNetwork.chainId != newNetworkId) + { + viewModel.setNetwork(newNetworkId); + viewModel.updateGasPrice(newNetworkId); + } + //refresh URL page + //reloadPage(); + } + + private void displayError(int title, int text) + { + if (resultDialog != null && resultDialog.isShowing()) resultDialog.dismiss(); + resultDialog = new AWalletAlertDialog(this); + resultDialog.setIcon(ERROR); + resultDialog.setTitle(title); + if (text != 0) resultDialog.setMessage(text); + resultDialog.setButtonText(R.string.button_ok); + resultDialog.setButtonListener(v -> { + resultDialog.dismiss(); + }); + resultDialog.show(); + + if (confirmationDialog != null && confirmationDialog.isShowing()) + confirmationDialog.dismiss(); + } + + private void changeChainRequest(long callbackId, NetworkInfo info) + { + //Don't show dialog if network doesn't need to be changed or if already showing + if ((activeNetwork != null && activeNetwork.chainId == info.chainId) || (chainSwapDialog != null && chainSwapDialog.isShowing())) + { + tokenScriptView.onWalletActionSuccessful(callbackId, null); + return; + } + + activeNetwork = info; + tokenScriptView.setChainId(info.chainId, false); + viewModel.setNetwork(info.chainId); + tokenScriptView.onWalletActionSuccessful(callbackId, null); + } + + @Override + public void onRequestAccounts(long callbackId) + { + Timber.tag("TOKENSCRIPT").w("Received account request"); + //TODO: Pop open dialog which asks user to confirm they wish to expose their address to this dapp eg: + //title = "Request Account Address" + //message = "${dappUrl} requests your address. \nAuthorise?" + //if user authorises, then do an evaluateJavascript to populate the web3.eth.getCoinbase with the current address, + //and additionally add a window.ethereum.setAddress function in init.js to set up addresses + //together with this update, also need to track which websites have been given permission, and if they already have it (can probably get away with using SharedPrefs) + //then automatically perform with step without a dialog (ie same as it does currently) + tokenScriptView.onWalletActionSuccessful(callbackId, "[\"" + wallet.address + "\"]"); + } + + //EIP-3326 + @Override + public void onWalletSwitchEthereumChain(long callbackId, WalletAddEthereumChainObject chainObj) + { + //request user to change chains + long chainId = chainObj.getChainId(); + + final NetworkInfo info = viewModel.getNetworkInfo(chainId); + + if (info == null) + { + chainSwapDialog = new AWalletAlertDialog(this); + chainSwapDialog.setTitle(R.string.unknown_network_title); + chainSwapDialog.setMessage(getString(R.string.unknown_network, String.valueOf(chainId))); + chainSwapDialog.setButton(R.string.dialog_ok, v -> { + if (chainSwapDialog.isShowing()) chainSwapDialog.dismiss(); + }); + chainSwapDialog.setSecondaryButton(R.string.action_cancel, v -> chainSwapDialog.dismiss()); + chainSwapDialog.setCancelable(false); + chainSwapDialog.show(); + } + else + { + changeChainRequest(callbackId, info); + } + } + + private void handleSignMessage(Signable message) + { + if (message.getMessageType() == SignMessageType.SIGN_TYPED_DATA_V3 && message.getChainId() != activeNetwork.chainId) + { + showErrorDialogIncompatibleNetwork(message.getCallbackId(), message.getChainId(), activeNetwork.chainId); + } + else if (confirmationDialog == null || !confirmationDialog.isShowing()) + { + confirmationDialog = new ActionSheetSignDialog(this, this, message); + confirmationDialog.show(); + } + } + + private void showErrorDialogIncompatibleNetwork(long callbackId, long requestingChainId, long activeChainId) + { + if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) + { + errorDialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); + String message = com.alphawallet.app.repository.EthereumNetworkBase.isChainSupported(requestingChainId) ? + getString(R.string.error_eip712_incompatible_network, + com.alphawallet.app.repository.EthereumNetworkBase.getShortChainName(requestingChainId), + com.alphawallet.app.repository.EthereumNetworkBase.getShortChainName(activeChainId)) : + getString(R.string.error_eip712_unsupported_network, String.valueOf(requestingChainId)); + errorDialog.setMessage(message); + errorDialog.setButton(R.string.action_cancel, v -> { + errorDialog.dismiss(); + dismissed("", callbackId, false); + }); + errorDialog.setCancelable(false); + errorDialog.show(); + + viewModel.trackError(Analytics.Error.BROWSER, message); + } + } + + @Override + public void signingComplete(SignatureFromKey signature, Signable message) + { + String signHex = Numeric.toHexString(signature.signature); + Timber.d("Initial Msg: %s", message.getMessage()); + confirmationDialog.success(); + tokenScriptView.onSignMessageSuccessful(message, signHex); + } + + @Override + public void signingFailed(Throwable error, Signable message) + { + tokenScriptView.onSignCancel(message.getCallbackId()); + confirmationDialog.dismiss(); + } + + @Override + public void onSignTransaction(Web3Transaction transaction, String url) + { + try + { + //minimum for transaction to be valid: recipient and value or payload + if ((confirmationDialog == null || !confirmationDialog.isShowing()) && + (transaction.recipient.equals(Address.EMPTY) && transaction.payload != null) // Constructor + || (!transaction.recipient.equals(Address.EMPTY) && (transaction.payload != null || transaction.value != null))) // Raw or Function TX + { + Token token = viewModel.getTokenService().getTokenOrBase(activeNetwork.chainId, transaction.recipient.toString()); + confirmationDialog = new ActionSheetDialog(this, transaction, token, + "", transaction.recipient.toString(), viewModel.getTokenService(), this); + ((ActionSheetDialog) confirmationDialog).setDappSigningMode(); + confirmationDialog.setURL(url); + confirmationDialog.setCanceledOnTouchOutside(false); + confirmationDialog.show(); + confirmationDialog.fullExpand(); + + viewModel.calculateGasEstimate(wallet, transaction, activeNetwork.chainId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(estimate -> confirmationDialog.setGasEstimate(estimate), + Throwable::printStackTrace) + .isDisposed(); + + return; + } + } + catch (Exception e) + { + e.printStackTrace(); + } + + onInvalidTransaction(transaction); + tokenScriptView.onSignCancel(transaction.leafPosition); + } + + private void onInvalidTransaction(Web3Transaction transaction) + { + resultDialog = new AWalletAlertDialog(this); + resultDialog.setIcon(AWalletAlertDialog.ERROR); + resultDialog.setTitle(getString(R.string.invalid_transaction)); + + if (transaction.recipient.equals(Address.EMPTY) && (transaction.payload == null || transaction.value != null)) + { + resultDialog.setMessage(getString(R.string.contains_no_recipient)); + } + else if (transaction.payload == null && transaction.value == null) + { + resultDialog.setMessage(getString(R.string.contains_no_value)); + } + else + { + resultDialog.setMessage(getString(R.string.contains_no_data)); + } + resultDialog.setButtonText(R.string.button_ok); + resultDialog.setButtonListener(v -> { + resultDialog.dismiss(); + }); + resultDialog.setCancelable(true); + resultDialog.show(); + } + +} diff --git a/app/src/main/java/com/alphawallet/app/ui/TransferNFTActivity.java b/app/src/main/java/com/alphawallet/app/ui/TransferNFTActivity.java index 3038881dac..2dcb07bb12 100644 --- a/app/src/main/java/com/alphawallet/app/ui/TransferNFTActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/TransferNFTActivity.java @@ -146,16 +146,19 @@ protected void onCreate(@Nullable Bundle savedInstanceState) functionBar.revealButtons(); setupScreen(); - - confirmRemoveShortcuts(assetSelection, token); } - private void confirmRemoveShortcuts(ArrayList> tokenIdList, Token token) + private boolean confirmRemoveShortcuts(ArrayList> tokenIdList, Token token) { List shortcutIds = ShortcutUtils.getShortcutIds(getApplicationContext(), token, tokenIdList); if (!shortcutIds.isEmpty()) { - ShortcutUtils.showConfirmationDialog(this, shortcutIds, getString(R.string.remove_shortcut_reminder)); + ShortcutUtils.showConfirmationDialog(this, shortcutIds, getString(R.string.remove_shortcut_reminder), this); + return true; + } + else + { + return false; } } @@ -334,8 +337,11 @@ public void addressReady(String address, String ensName) @Override public void showTransferToken(List selection) { - KeyboardUtils.hideKeyboard(getCurrentFocus()); - addressInput.getAddress(); + if (!confirmRemoveShortcuts(assetSelection, token)) + { + KeyboardUtils.hideKeyboard(getCurrentFocus()); + addressInput.getAddress(); + } } private void calculateTransactionCost() @@ -381,7 +387,6 @@ private void calculateEstimateDialog() */ private void checkConfirm(GasEstimate estimate, final byte[] transactionBytes, final String txSendAddress, final String resolvedAddress) { - Web3Transaction w3tx = new Web3Transaction( new Address(txSendAddress), new Address(token.getAddress()), diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/entity/NFTAttributeLayout.java b/app/src/main/java/com/alphawallet/app/ui/widget/entity/NFTAttributeLayout.java index 258cc2eebc..b12093a588 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/entity/NFTAttributeLayout.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/entity/NFTAttributeLayout.java @@ -1,6 +1,7 @@ package com.alphawallet.app.ui.widget.entity; import android.content.Context; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; @@ -64,7 +65,7 @@ public void bindTSAttributes(List attrs) private void setAttributeLabel(String tokenName, int size) { - if (size > 0 && tokenName.equalsIgnoreCase("cryptokitties")) + if (size > 0 && !TextUtils.isEmpty(tokenName) && tokenName.equalsIgnoreCase("cryptokitties")) { labelAttributes.setTitle(getContext().getString(R.string.label_cattributes)); labelAttributes.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/alphawallet/app/util/ShortcutUtils.java b/app/src/main/java/com/alphawallet/app/util/ShortcutUtils.java index 2227dcd21e..51ee3b5bd8 100644 --- a/app/src/main/java/com/alphawallet/app/util/ShortcutUtils.java +++ b/app/src/main/java/com/alphawallet/app/util/ShortcutUtils.java @@ -10,6 +10,7 @@ import androidx.core.graphics.drawable.IconCompat; import com.alphawallet.app.R; +import com.alphawallet.app.entity.StandardFunctionInterface; import com.alphawallet.app.entity.nftassets.NFTAsset; import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.repository.EthereumNetworkRepository; @@ -59,7 +60,7 @@ public static ArrayList getShortcutIds(Context context, Token token, Lis return ids; } - public static void showConfirmationDialog(Activity activity, List shortcutIds, String message) + public static void showConfirmationDialog(Activity activity, List shortcutIds, String message, StandardFunctionInterface callback) { AWalletAlertDialog confirmationDialog = new AWalletAlertDialog(activity); confirmationDialog.setCancelable(false); @@ -68,7 +69,7 @@ public static void showConfirmationDialog(Activity activity, List shortc confirmationDialog.setButton(R.string.yes_continue, v -> { ShortcutManagerCompat.removeDynamicShortcuts(activity, shortcutIds); confirmationDialog.dismiss(); - activity.finish(); + callback.showTransferToken(new ArrayList<>()); }); confirmationDialog.setSecondaryButtonText(R.string.dialog_cancel_back); confirmationDialog.setSecondaryButtonListener(v -> { diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/AdvancedSettingsViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/AdvancedSettingsViewModel.java index 5ac7cea64f..f30c994d8c 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/AdvancedSettingsViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/AdvancedSettingsViewModel.java @@ -84,4 +84,14 @@ public void stopChainActivity() { transactionsService.stopActivity(); } + + public void toggleUseViewer(boolean state) + { + preferenceRepository.setUseTSViewer(state); + } + + public boolean getTokenScriptViewerState() + { + return preferenceRepository.getUseTSViewer(); + } } diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/NFTAssetsViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/NFTAssetsViewModel.java index b849f97d8d..23bbbedadc 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/NFTAssetsViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/NFTAssetsViewModel.java @@ -6,7 +6,6 @@ import com.alphawallet.app.C; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.nftassets.NFTAsset; -import com.alphawallet.app.entity.tokens.Attestation; import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.interact.FetchTransactionsInteract; import com.alphawallet.app.service.AssetDefinitionService; @@ -16,7 +15,6 @@ import com.alphawallet.app.ui.NFTAssetDetailActivity; import java.math.BigInteger; -import java.util.List; import javax.inject.Inject; 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 9cda86cbce..d4c877daa8 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/TokenFunctionViewModel.java @@ -34,6 +34,7 @@ import com.alphawallet.app.interact.FetchTransactionsInteract; import com.alphawallet.app.interact.GenericWalletInteract; import com.alphawallet.app.repository.EthereumNetworkRepositoryType; +import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.service.AnalyticsServiceType; import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.GasService; @@ -104,11 +105,11 @@ public class TokenFunctionViewModel extends BaseViewModel implements Transaction private final CreateTransactionInteract createTransactionInteract; private final GasService gasService; private final TokensService tokensService; - private final EthereumNetworkRepositoryType ethereumNetworkRepository; private final KeyService keyService; private final GenericWalletInteract genericWalletInteract; private final OpenSeaService openseaService; private final FetchTransactionsInteract fetchTransactionsInteract; + private final PreferenceRepositoryType preferences; private final MutableLiveData insufficientFunds = new MutableLiveData<>(); private final MutableLiveData invalidAddress = new MutableLiveData<>(); private final MutableLiveData sig = new MutableLiveData<>(); @@ -144,23 +145,23 @@ public class TokenFunctionViewModel extends BaseViewModel implements Transaction CreateTransactionInteract createTransactionInteract, GasService gasService, TokensService tokensService, - EthereumNetworkRepositoryType ethereumNetworkRepository, KeyService keyService, GenericWalletInteract genericWalletInteract, OpenSeaService openseaService, FetchTransactionsInteract fetchTransactionsInteract, - AnalyticsServiceType analyticsService) + AnalyticsServiceType analyticsService, + PreferenceRepositoryType prefs) { this.assetDefinitionService = assetDefinitionService; this.createTransactionInteract = createTransactionInteract; this.gasService = gasService; this.tokensService = tokensService; - this.ethereumNetworkRepository = ethereumNetworkRepository; this.keyService = keyService; this.genericWalletInteract = genericWalletInteract; this.openseaService = openseaService; this.fetchTransactionsInteract = fetchTransactionsInteract; setAnalyticsService(analyticsService); + this.preferences = prefs; } public AssetDefinitionService getAssetDefinitionService() @@ -1034,4 +1035,9 @@ public GasService getGasService() { return gasService; } + + public boolean getUseTSViewer() + { + return preferences.getUseTSViewer(); + } } diff --git a/app/src/main/java/com/alphawallet/app/web3/Web3View.java b/app/src/main/java/com/alphawallet/app/web3/Web3View.java index 71e0270680..58ca2c668e 100644 --- a/app/src/main/java/com/alphawallet/app/web3/Web3View.java +++ b/app/src/main/java/com/alphawallet/app/web3/Web3View.java @@ -225,9 +225,13 @@ public long getChainId() return webViewClient.getJsInjectorClient().getChainId(); } - public void setChainId(long chainId) + public void setChainId(long chainId, boolean isTokenscript) { - webViewClient.getJsInjectorClient().setChainId(chainId); + if (isTokenscript){ + webViewClient.getJsInjectorClient().setTSChainId(chainId); + } else { + webViewClient.getJsInjectorClient().setChainId(chainId); + } } public void setWebLoadCallback(URLLoadInterface iFace) diff --git a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java index 15ebd1fc04..0572b884f2 100644 --- a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java @@ -360,6 +360,11 @@ public void setSignOnly() use1559Transactions = !candidateTransaction.isLegacyTransaction(); // If doing a sign-only transaction (not send) we must respect ERC-1559 or legacy. } + public void setDappSigningMode() + { + this.mode = ActionSheetMode.SEND_TRANSACTION_DAPP; + } + public void onDestroy() { if (gasWidgetInterface != null) gasWidgetInterface.onDestroy(); 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 fd4cfc3e93..0435d46079 100644 --- a/app/src/main/java/com/alphawallet/app/widget/FunctionButtonBar.java +++ b/app/src/main/java/com/alphawallet/app/widget/FunctionButtonBar.java @@ -41,7 +41,6 @@ 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; import com.alphawallet.app.repository.OnRampRepositoryType; import com.alphawallet.app.service.AssetDefinitionService; @@ -166,7 +165,7 @@ public void setupFunctions(StandardFunctionInterface functionInterface, AssetDef callStandardFunctions = functionInterface; adapter = adp; selection.clear(); - if (tokenIds != null) selection.addAll(tokenIds); + addTokenSelection(tokenIds); resetButtonCount(); this.token = token; functions = assetSvs.getTokenFunctionMap(token); @@ -174,12 +173,29 @@ public void setupFunctions(StandardFunctionInterface functionInterface, AssetDef getFunctionMap(assetSvs, token.getInterfaceSpec()); } + public void setupFunctionsForJsViewer(StandardFunctionInterface functionInterface, int functionNameResource, Token token, List tokenIds) + { + callStandardFunctions = functionInterface; + adapter = null; + functions = null; + addTokenSelection(tokenIds); + resetButtonCount(); + //buttonCount = 2; + this.token = token; + addFunction(functionNameResource); + + this.addStandardTokenFunctions(token); + + //always show buttons + findViewById(R.id.layoutButtons).setVisibility(View.VISIBLE); + } + public void setupAttestationFunctions(StandardFunctionInterface functionInterface, AssetDefinitionService assetSvs, Token token, NonFungibleAdapterInterface adp, List tokenIds) { callStandardFunctions = functionInterface; adapter = adp; selection.clear(); - selection.addAll(tokenIds); + addTokenSelection(tokenIds); resetButtonCount(); this.token = token; functions = assetSvs.getAttestationFunctionMap(token); @@ -237,6 +253,20 @@ private void onMoreButtonClick() bottomSheet.show(); } + private void addTokenSelection(List tokenIds) + { + if (tokenIds != null) + { + for (BigInteger tokenId : tokenIds) + { + if (!selection.contains(tokenId)) + { + selection.add(tokenId); + } + } + } + } + private void handleAction(ItemClick action) { if (functions != null && functions.containsKey(action.buttonText)) @@ -394,7 +424,7 @@ public void onTokenClick(View view, Token token, List tokenIds, bool if (maxSelect <= 1) { selection.clear(); - selection.addAll(tokenIds); + addTokenSelection(tokenIds); if (adapter != null) adapter.setRadioButtons(true); } } @@ -406,7 +436,7 @@ public void onLongTokenClick(View view, Token token, List tokenIds) if (adapter != null) adapter.setRadioButtons(true); selection.clear(); - selection.addAll(tokenIds); + addTokenSelection(tokenIds); Vibrator vb = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); if (vb != null && vb.hasVibrator()) { diff --git a/app/src/main/res/drawable/ic_tokenscript.xml b/app/src/main/res/drawable/ic_tokenscript.xml new file mode 100644 index 0000000000..d2b8a37208 --- /dev/null +++ b/app/src/main/res/drawable/ic_tokenscript.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_generic_settings.xml b/app/src/main/res/layout/activity_generic_settings.xml index 26580f474d..365d0df248 100644 --- a/app/src/main/res/layout/activity_generic_settings.xml +++ b/app/src/main/res/layout/activity_generic_settings.xml @@ -1,16 +1,23 @@ + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> - + + + + + diff --git a/app/src/main/res/layout/activity_tokenscript_js.xml b/app/src/main/res/layout/activity_tokenscript_js.xml new file mode 100644 index 0000000000..f2e95c8777 --- /dev/null +++ b/app/src/main/res/layout/activity_tokenscript_js.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 82505209c0..b26f4e91f0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -996,4 +996,5 @@ Anulación del Desarrollador Es posible que esté a punto de firmar una transacción sin saberlo, lo que podría vaciar sus fondos. Es posible que desee firmar el código de bytes como desarrollador y puede anular esta advertencia si configura el modo de desarrollador en la configuración avanzada. Constructor + Visor de TokenScript diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 1278eaed8a..e6bdcd664e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1010,4 +1010,5 @@ Remplacement du Développeur Vous êtes peut-être sur le point de signer sans le savoir une transaction, ce qui pourrait vider vos fonds. Vous souhaiterez peut-être signer le bytecode en tant que développeur et vous pouvez ignorer cet avertissement si vous définissez le mode développeur dans les paramètres avancés. Constructor + Visionneuse TokenScript diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index bee1732771..b3ff80df53 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -1001,4 +1001,5 @@ Penggantian Pengembang Anda mungkin tanpa sadar menandatangani transaksi, yang dapat mengosongkan dana Anda. Anda mungkin ingin menandatangani bytecode sebagai pengembang, dan Anda dapat mengabaikan peringatan ini jika Anda menyetel mode pengembang di Setelan lanjutan. Constructor + Penampil TokenScript diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index 4f342d8668..0be4e4fedd 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -1031,4 +1031,5 @@ Developer Override သင့်ငွေများကို အချည်းနှီးဖြစ်စေနိုင်သည့် ငွေပေးငွေယူတစ်ခုအား သင်မသိလိုက်ဘဲ လက်မှတ်ထိုးပါတော့မည်။ သင်သည် ဆော့ဖ်ဝဲအင်ဂျင်နီယာတစ်ဦးအနေဖြင့် bytecode ကို လက်မှတ်ထိုးလိုနိုင်ပြီး၊ Advanced ဆက်တင်များတွင် developer မုဒ်ကို သင်သတ်မှတ်ပါက ဤသတိပေးချက်ကို အစားထိုးနိုင်ပါသည်။ Constructor + TokenScript ကြည့်ရှုသူ diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 6efc66ab3b..bf006616dd 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1010,4 +1010,5 @@ Ghi đè Nhà phát triển Bạn có thể sắp vô tình ký một giao dịch, điều này có thể khiến tiền của bạn bị rỗng. Bạn có thể muốn ký mã byte với tư cách là nhà phát triển và bạn có thể ghi đè cảnh báo này nếu bạn đặt chế độ nhà phát triển trong cài đặt Nâng cao. Constructor + Trình xem TokenScript diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index e384267605..d2231121d8 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -997,4 +997,5 @@ 开发者覆盖 您可能会在不知情的情况下签署一项交易,这可能会清空您的资金。 您可能希望以开发人员的身份对字节码进行签名,如果您在高级设置中设置开发人员模式,则可以覆盖此警告。 Constructor + 使用 TokenScript 查看器 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 23ff05a6d3..8874345072 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1073,4 +1073,5 @@ Developer Override You might be about to unknowingly sign a transaction, which could empty your funds. You may want to sign bytecode as a developer, and you can override this warning if you set developer mode in Advanced settings. Constructor + Use TokenScript Viewer