diff --git a/app/src/androidTest/java/com/alphawallet/app/AnalyticsSettingsTest.java b/app/src/androidTest/java/com/alphawallet/app/AnalyticsSettingsTest.java index e27a2d8c7..948deebdb 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.assertions.Should.shouldSee; 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 class AnalyticsSettingsTest extends BaseE2ETest 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 54b203799..8d9c1a893 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 @@ public class AdvancedSettingsActivity extends BaseActivity 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 @@ public class AdvancedSettingsActivity extends BaseActivity 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 40ca8201d..080b4eb2e 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 @@ public class DappBrowserFragment extends BaseFragment implements OnSignTransacti 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 9998f3838..56f2ad747 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 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc 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 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc 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 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc 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 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc 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 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc { setupAttestation(td); } - else + else if (this.usingNativeTokenScript) { displayTokenView(td); } @@ -389,7 +404,16 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc 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 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc 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 000000000..6dbefd229 --- /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 3038881da..2dcb07bb1 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 @@ public class TransferNFTActivity extends BaseActivity implements TokensAdapterCa 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 class TransferNFTActivity extends BaseActivity implements TokensAdapterCa @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 @@ public class TransferNFTActivity extends BaseActivity implements TokensAdapterCa */ 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 258cc2eeb..b12093a58 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 class NFTAttributeLayout extends LinearLayout { 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 2227dcd21..51ee3b5bd 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.content.pm.ShortcutManagerCompat; 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 class ShortcutUtils 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 class ShortcutUtils 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 5ac7cea64..f30c994d8 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 class AdvancedSettingsViewModel extends BaseViewModel { { 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 b849f97d8..23bbbedad 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 android.content.Intent; 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.Erc1155AssetListActivity; 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 9cda86cbc..d4c877daa 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.CreateTransactionInteract; 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 class TokenFunctionViewModel extends BaseViewModel implements Transaction { 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 71e027068..58ca2c668 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 class Web3View extends WebView { 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 15ebd1fc0..0572b884f 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 class ActionSheetDialog extends ActionSheet implements StandardFunctionIn 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 fd4cfc3e9..0435d4607 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.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; import com.alphawallet.app.repository.OnRampRepositoryType; import com.alphawallet.app.service.AssetDefinitionService; @@ -166,7 +165,7 @@ public class FunctionButtonBar extends LinearLayout implements AdapterView.OnIte 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 class FunctionButtonBar extends LinearLayout implements AdapterView.OnIte 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 @@ public class FunctionButtonBar extends LinearLayout implements AdapterView.OnIte 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 class FunctionButtonBar extends LinearLayout implements AdapterView.OnIte if (maxSelect <= 1) { selection.clear(); - selection.addAll(tokenIds); + addTokenSelection(tokenIds); if (adapter != null) adapter.setRadioButtons(true); } } @@ -406,7 +436,7 @@ public class FunctionButtonBar extends LinearLayout implements AdapterView.OnIte 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 000000000..d2b8a3720 --- /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 26580f474..365d0df24 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 000000000..f2e95c877 --- /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 82505209c..b26f4e91f 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 1278eaed8..e6bdcd664 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 bee173277..b3ff80df5 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 4f342d866..0be4e4fed 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 6efc66ab3..bf006616d 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 e38426760..d2231121d 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 23ff05a6d..887434507 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