From 8fca478335d4a99ac5ccbeb0b8e1ba022d6c6ef0 Mon Sep 17 00:00:00 2001 From: justindg Date: Mon, 24 Apr 2023 17:49:03 -0700 Subject: [PATCH 01/32] Rename LI.FI Token entity to avoid confusion --- .../alphawallet/app/entity/lifi/Action.java | 4 +- .../app/entity/lifi/Connection.java | 4 +- .../alphawallet/app/entity/lifi/FeeCost.java | 4 +- .../alphawallet/app/entity/lifi/GasCost.java | 2 +- .../lifi/{Token.java => LifiToken.java} | 4 +- .../alphawallet/app/service/SwapService.java | 18 ++++----- .../com/alphawallet/app/ui/SwapActivity.java | 40 +++++++++---------- ...apter.java => SelectLifiTokenAdapter.java} | 16 ++++---- .../app/ui/widget/adapter/TokenFilter.java | 18 ++++----- .../app/viewmodel/SwapViewModel.java | 12 +++--- ...Dialog.java => SelectLifiTokenDialog.java} | 18 ++++----- .../alphawallet/app/widget/TokenSelector.java | 10 ++--- .../app/entity/lifi/ActionTest.java | 4 +- .../{TokenTest.java => LifiTokenTest.java} | 6 +-- ...lterTest.java => LifiTokenFilterTest.java} | 25 ++++++------ .../alphawallet/app/util/SwapUtilsTest.java | 15 ++++--- .../alphawallet/app/viewmodel/TokensTest.java | 15 ++++--- 17 files changed, 105 insertions(+), 110 deletions(-) rename app/src/main/java/com/alphawallet/app/entity/lifi/{Token.java => LifiToken.java} (97%) rename app/src/main/java/com/alphawallet/app/ui/widget/adapter/{SelectTokenAdapter.java => SelectLifiTokenAdapter.java} (84%) rename app/src/main/java/com/alphawallet/app/widget/{SelectTokenDialog.java => SelectLifiTokenDialog.java} (83%) rename app/src/test/java/com/alphawallet/app/entity/lifi/{TokenTest.java => LifiTokenTest.java} (88%) rename app/src/test/java/com/alphawallet/app/ui/widget/adapter/{TokenFilterTest.java => LifiTokenFilterTest.java} (77%) diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Action.java b/app/src/main/java/com/alphawallet/app/entity/lifi/Action.java index 110be10c1a..2a4b25b149 100644 --- a/app/src/main/java/com/alphawallet/app/entity/lifi/Action.java +++ b/app/src/main/java/com/alphawallet/app/entity/lifi/Action.java @@ -18,11 +18,11 @@ public class Action @SerializedName("fromToken") @Expose - public Token fromToken; + public LifiToken fromToken; @SerializedName("toToken") @Expose - public Token toToken; + public LifiToken toToken; @SerializedName("fromAmount") @Expose diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Connection.java b/app/src/main/java/com/alphawallet/app/entity/lifi/Connection.java index 01fe4a324c..b1d853e7f0 100644 --- a/app/src/main/java/com/alphawallet/app/entity/lifi/Connection.java +++ b/app/src/main/java/com/alphawallet/app/entity/lifi/Connection.java @@ -17,9 +17,9 @@ public class Connection @SerializedName("fromTokens") @Expose - public List fromTokens; + public List fromTokens; @SerializedName("toTokens") @Expose - public List toTokens; + public List toTokens; } diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/FeeCost.java b/app/src/main/java/com/alphawallet/app/entity/lifi/FeeCost.java index bbbd5cd645..5a68b58ca0 100644 --- a/app/src/main/java/com/alphawallet/app/entity/lifi/FeeCost.java +++ b/app/src/main/java/com/alphawallet/app/entity/lifi/FeeCost.java @@ -3,8 +3,6 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; -import java.util.ArrayList; - public class FeeCost { @SerializedName("name") @@ -17,7 +15,7 @@ public class FeeCost @SerializedName("token") @Expose - public Token token; + public LifiToken token; @SerializedName("amount") @Expose diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/GasCost.java b/app/src/main/java/com/alphawallet/app/entity/lifi/GasCost.java index d2ccd5b39b..f504311c7f 100644 --- a/app/src/main/java/com/alphawallet/app/entity/lifi/GasCost.java +++ b/app/src/main/java/com/alphawallet/app/entity/lifi/GasCost.java @@ -15,5 +15,5 @@ public class GasCost @SerializedName("token") @Expose - public Token token; + public LifiToken token; } \ No newline at end of file diff --git a/app/src/main/java/com/alphawallet/app/entity/lifi/Token.java b/app/src/main/java/com/alphawallet/app/entity/lifi/LifiToken.java similarity index 97% rename from app/src/main/java/com/alphawallet/app/entity/lifi/Token.java rename to app/src/main/java/com/alphawallet/app/entity/lifi/LifiToken.java index 83ec1225a6..04d86cd78a 100644 --- a/app/src/main/java/com/alphawallet/app/entity/lifi/Token.java +++ b/app/src/main/java/com/alphawallet/app/entity/lifi/LifiToken.java @@ -5,7 +5,7 @@ import java.util.Objects; -public class Token +public class LifiToken { @SerializedName("address") @Expose @@ -47,7 +47,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - Token lToken = (Token) o; + LifiToken lToken = (LifiToken) o; return address.equals(lToken.address) && symbol.equals(lToken.symbol); } diff --git a/app/src/main/java/com/alphawallet/app/service/SwapService.java b/app/src/main/java/com/alphawallet/app/service/SwapService.java index fab5804e2e..3f58f77f15 100644 --- a/app/src/main/java/com/alphawallet/app/service/SwapService.java +++ b/app/src/main/java/com/alphawallet/app/service/SwapService.java @@ -4,7 +4,7 @@ import com.alphawallet.app.C; import com.alphawallet.app.entity.lifi.RouteOptions; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import com.alphawallet.app.repository.SwapRepository; import com.alphawallet.app.util.BalanceUtils; import com.alphawallet.app.util.JsonUtils; @@ -125,8 +125,8 @@ public Single getConnections(long from, long to) return Single.fromCallable(() -> fetchPairs(from, to)); } - public Single getQuote(Token source, - Token dest, + public Single getQuote(LifiToken source, + LifiToken dest, String address, String amount, String slippage, @@ -135,8 +135,8 @@ public Single getQuote(Token source, return Single.fromCallable(() -> fetchQuote(source, dest, address, amount, slippage, allowExchanges)); } - public Single getRoutes(Token source, - Token dest, + public Single getRoutes(LifiToken source, + LifiToken dest, String address, String amount, String slippage, @@ -180,8 +180,8 @@ public String fetchPairs(long fromChain, long toChain) return executeRequest(builder.build().toString()); } - public String fetchQuote(Token source, - Token dest, + public String fetchQuote(LifiToken source, + LifiToken dest, String address, String amount, String slippage, @@ -200,8 +200,8 @@ public String fetchQuote(Token source, return executeRequest(builder.build().toString()); } - public String fetchRoutes(Token source, - Token dest, + public String fetchRoutes(LifiToken source, + LifiToken dest, String address, String amount, String slippage, diff --git a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java index 7f001e3465..a916c9c977 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java @@ -32,7 +32,7 @@ import com.alphawallet.app.entity.lifi.Chain; import com.alphawallet.app.entity.lifi.Connection; import com.alphawallet.app.entity.lifi.Quote; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; import com.alphawallet.app.ui.widget.entity.ProgressInfo; import com.alphawallet.app.util.BalanceUtils; @@ -42,7 +42,7 @@ import com.alphawallet.app.web3.entity.Web3Transaction; import com.alphawallet.app.widget.AWalletAlertDialog; import com.alphawallet.app.widget.ActionSheetDialog; -import com.alphawallet.app.widget.SelectTokenDialog; +import com.alphawallet.app.widget.SelectLifiTokenDialog; import com.alphawallet.app.widget.StandardHeader; import com.alphawallet.app.widget.SwapSettingsDialog; import com.alphawallet.app.widget.TokenInfoView; @@ -65,8 +65,8 @@ public class SwapActivity extends BaseActivity implements StandardFunctionInterf private SwapViewModel viewModel; private TokenSelector sourceSelector; private TokenSelector destSelector; - private SelectTokenDialog sourceTokenDialog; - private SelectTokenDialog destTokenDialog; + private SelectLifiTokenDialog sourceTokenDialog; + private SelectLifiTokenDialog destTokenDialog; private ActionSheetDialog confirmationDialog; private SwapSettingsDialog settingsDialog; private AWalletAlertDialog progressDialog; @@ -86,7 +86,7 @@ public class SwapActivity extends BaseActivity implements StandardFunctionInterf private TextView chainName; private com.alphawallet.app.entity.tokens.Token token; private Wallet wallet; - private Token sourceToken; + private LifiToken sourceToken; private List chains; private String selectedRouteProvider; private CountDownTimer getQuoteTimer; @@ -256,7 +256,7 @@ public void onAmountChanged(String amount) } @Override - public void onSelectionChanged(Token token) + public void onSelectionChanged(LifiToken token) { sourceTokenChanged(token); } @@ -264,7 +264,7 @@ public void onSelectionChanged(Token token) @Override public void onMaxClicked() { - Token token = sourceSelector.getToken(); + LifiToken token = sourceSelector.getToken(); if (token == null) { return; @@ -293,7 +293,7 @@ public void onAmountChanged(String amount) } @Override - public void onSelectionChanged(Token token) + public void onSelectionChanged(LifiToken token) { destTokenChanged(token); } @@ -342,7 +342,7 @@ private ActionSheetDialog createConfirmationAction(Quote quote) return confDialog; } - private void destTokenChanged(Token token) + private void destTokenChanged(LifiToken token) { destSelector.setBalance(viewModel.getBalance(token)); @@ -355,7 +355,7 @@ private void destTokenChanged(Token token) getAvailableRoutes(); } - private void sourceTokenChanged(Token token) + private void sourceTokenChanged(LifiToken token) { if (destSelector.getToken() == null) { @@ -404,7 +404,7 @@ protected void onPause() } // The source token should default to the token selected in the main wallet dialog (ie the token from the intent). - private void initSourceToken(Token selectedToken) + private void initSourceToken(LifiToken selectedToken) { if (selectedToken != null) { @@ -418,20 +418,20 @@ private void initSourceToken(Token selectedToken) } } - private void initFromDialog(List fromTokens) + private void initFromDialog(List fromTokens) { Tokens.sortValue(fromTokens); - sourceTokenDialog = new SelectTokenDialog(fromTokens, this, tokenItem -> { + sourceTokenDialog = new SelectLifiTokenDialog(fromTokens, this, tokenItem -> { sourceSelector.init(tokenItem); sourceTokenDialog.dismiss(); }); } - private void initToDialog(List toTokens) + private void initToDialog(List toTokens) { Tokens.sortName(toTokens); Tokens.sortValue(toTokens); - destTokenDialog = new SelectTokenDialog(toTokens, this, tokenItem -> { + destTokenDialog = new SelectLifiTokenDialog(toTokens, this, tokenItem -> { destSelector.init(tokenItem); destTokenDialog.dismiss(); }); @@ -509,13 +509,13 @@ private void onConnections(List connections) { if (!connections.isEmpty()) { - List fromTokens = new ArrayList<>(); - List toTokens = new ArrayList<>(); - Token selectedToken = null; + List fromTokens = new ArrayList<>(); + List toTokens = new ArrayList<>(); + LifiToken selectedToken = null; for (Connection c : connections) { - for (Token t : c.fromTokens) + for (LifiToken t : c.fromTokens) { if (!fromTokens.contains(t)) { @@ -534,7 +534,7 @@ private void onConnections(List connections) } } - for (Token t : c.toTokens) + for (LifiToken t : c.toTokens) { if (!toTokens.contains(t)) { diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java similarity index 84% rename from app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java rename to app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java index c5f32d6305..1098ced035 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java @@ -10,22 +10,22 @@ import androidx.recyclerview.widget.RecyclerView; import com.alphawallet.app.R; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import com.alphawallet.app.widget.AddressIcon; -import com.alphawallet.app.widget.SelectTokenDialog; +import com.alphawallet.app.widget.SelectLifiTokenDialog; import com.google.android.material.radiobutton.MaterialRadioButton; import java.util.ArrayList; import java.util.List; -public class SelectTokenAdapter extends RecyclerView.Adapter +public class SelectLifiTokenAdapter extends RecyclerView.Adapter { - private final List displayData; - private final SelectTokenDialog.SelectTokenDialogEventListener callback; + private final List displayData; + private final SelectLifiTokenDialog.SelectLifiTokenDialogEventListener callback; private final TokenFilter tokenFilter; private String selectedTokenAddress; - public SelectTokenAdapter(List tokens, SelectTokenDialog.SelectTokenDialogEventListener callback) + public SelectLifiTokenAdapter(List tokens, SelectLifiTokenDialog.SelectLifiTokenDialogEventListener callback) { tokenFilter = new TokenFilter(tokens); this.callback = callback; @@ -46,7 +46,7 @@ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { - Token item = displayData.get(position); + LifiToken item = displayData.get(position); if (item != null) { holder.name.setText(item.name); @@ -79,7 +79,7 @@ public void filter(String keyword) updateList(tokenFilter.filterBy(keyword)); } - public void updateList(List filteredList) + public void updateList(List filteredList) { displayData.clear(); displayData.addAll(filteredList); diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java index 1e9fb39653..224681ceca 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java @@ -4,7 +4,7 @@ import androidx.annotation.NonNull; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import java.util.ArrayList; import java.util.List; @@ -13,9 +13,9 @@ public class TokenFilter { - private final List tokens; + private final List tokens; - public TokenFilter(List tokens) + public TokenFilter(List tokens) { this.tokens = tokens; removeBadTokens(); @@ -23,10 +23,10 @@ public TokenFilter(List tokens) private void removeBadTokens() { - ListIterator iterator = this.tokens.listIterator(); + ListIterator iterator = this.tokens.listIterator(); while (iterator.hasNext()) { - Token t = iterator.next(); + LifiToken t = iterator.next(); if (TextUtils.isEmpty(t.name) || TextUtils.isEmpty(t.symbol)) { iterator.remove(); @@ -34,13 +34,13 @@ private void removeBadTokens() } } - public List filterBy(String keyword) + public List filterBy(String keyword) { String lowerCaseKeyword = lowerCase(keyword); - List result = new ArrayList<>(); + List result = new ArrayList<>(); // First filter: Add all entries that start with the keyword on top of the list. - for (Token lToken : this.tokens) + for (LifiToken lToken : this.tokens) { String name = lowerCase(lToken.name); String symbol = lowerCase(lToken.symbol); @@ -52,7 +52,7 @@ public List filterBy(String keyword) } // Second filter: Add the rest of the entries that contain the keyword on top of the list. - for (Token lToken : this.tokens) + for (LifiToken lToken : this.tokens) { String name = lowerCase(lToken.name); String symbol = lowerCase(lToken.symbol); diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/SwapViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/SwapViewModel.java index a826abf71e..f030e1ac88 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/SwapViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/SwapViewModel.java @@ -17,7 +17,7 @@ import com.alphawallet.app.entity.lifi.Connection; import com.alphawallet.app.entity.lifi.Quote; import com.alphawallet.app.entity.lifi.SwapProvider; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import com.alphawallet.app.interact.CreateTransactionInteract; import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.repository.SwapRepositoryType; @@ -197,7 +197,7 @@ public void getConnections(long from, long to) .subscribe(this::onConnections, this::onConnectionsError); } - public void getQuote(Token source, Token dest, String address, String amount, String slippage, String allowExchanges) + public void getQuote(LifiToken source, LifiToken dest, String address, String amount, String slippage, String allowExchanges) { if (!isValidAmount(amount)) return; if (hasEnoughBalance(source, amount)) @@ -243,7 +243,7 @@ private void onQuoteError(Throwable t) postError(C.ErrorCode.SWAP_QUOTE_ERROR, Objects.requireNonNull(t.getMessage())); } - public boolean hasEnoughBalance(Token source, String amount) + public boolean hasEnoughBalance(LifiToken source, String amount) { BigDecimal bal = new BigDecimal(getBalance(source)); BigDecimal reqAmount = new BigDecimal(amount); @@ -354,7 +354,7 @@ private boolean isValidQuote(String result) && result.contains("tool"); } - public String getBalance(Token token) + public String getBalance(LifiToken token) { com.alphawallet.app.entity.tokens.Token t; if (token.isNativeToken()) @@ -442,8 +442,8 @@ protected void onCleared() public void getRoutes(Activity activity, ActivityResultLauncher launcher, - Token source, - Token dest, + LifiToken source, + LifiToken dest, String address, String amount, String slippage) diff --git a/app/src/main/java/com/alphawallet/app/widget/SelectTokenDialog.java b/app/src/main/java/com/alphawallet/app/widget/SelectLifiTokenDialog.java similarity index 83% rename from app/src/main/java/com/alphawallet/app/widget/SelectTokenDialog.java rename to app/src/main/java/com/alphawallet/app/widget/SelectLifiTokenDialog.java index bc2ce27ba8..ec2ba0365d 100644 --- a/app/src/main/java/com/alphawallet/app/widget/SelectTokenDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/SelectLifiTokenDialog.java @@ -19,23 +19,23 @@ import androidx.recyclerview.widget.RecyclerView; import com.alphawallet.app.R; -import com.alphawallet.app.entity.lifi.Token; -import com.alphawallet.app.ui.widget.adapter.SelectTokenAdapter; +import com.alphawallet.app.entity.lifi.LifiToken; +import com.alphawallet.app.ui.widget.adapter.SelectLifiTokenAdapter; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; import java.util.List; -public class SelectTokenDialog extends BottomSheetDialog +public class SelectLifiTokenDialog extends BottomSheetDialog { private final Handler handler = new Handler(Looper.getMainLooper()); private RecyclerView tokenList; - private SelectTokenAdapter adapter; + private SelectLifiTokenAdapter adapter; private LinearLayout searchLayout; private EditText search; private TextView noResultsText; - public SelectTokenDialog(@NonNull Activity activity) + public SelectLifiTokenDialog(@NonNull Activity activity) { super(activity); View view = View.inflate(getContext(), R.layout.dialog_select_token, null); @@ -56,13 +56,13 @@ public SelectTokenDialog(@NonNull Activity activity) btnClose.setOnClickListener(v -> dismiss()); } - public SelectTokenDialog(List tokenItems, Activity activity, SelectTokenDialogEventListener callback) + public SelectLifiTokenDialog(List tokenItems, Activity activity, SelectLifiTokenDialogEventListener callback) { this(activity); noResultsText.setVisibility(tokenItems.size() > 0 ? View.GONE : View.VISIBLE); - adapter = new SelectTokenAdapter(tokenItems, callback); + adapter = new SelectLifiTokenAdapter(tokenItems, callback); tokenList.setLayoutManager(new LinearLayoutManager(getContext())); tokenList.setAdapter(adapter); @@ -100,8 +100,8 @@ public void setSelectedToken(String address) adapter.setSelectedToken(address); } - public interface SelectTokenDialogEventListener + public interface SelectLifiTokenDialogEventListener { - void onChainSelected(Token tokenItem); + void onChainSelected(LifiToken tokenItem); } } diff --git a/app/src/main/java/com/alphawallet/app/widget/TokenSelector.java b/app/src/main/java/com/alphawallet/app/widget/TokenSelector.java index 4c74fb4ec2..6b4a061228 100644 --- a/app/src/main/java/com/alphawallet/app/widget/TokenSelector.java +++ b/app/src/main/java/com/alphawallet/app/widget/TokenSelector.java @@ -16,7 +16,7 @@ import androidx.core.content.ContextCompat; import com.alphawallet.app.R; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import com.alphawallet.app.util.Utils; import com.google.android.material.button.MaterialButton; @@ -35,7 +35,7 @@ public class TokenSelector extends LinearLayout private final TextView error; private Runnable runnable; private TokenSelectorEventListener callback; - private Token tokenItem; + private LifiToken tokenItem; public TokenSelector(Context context, AttributeSet attrs) { @@ -143,7 +143,7 @@ public void reset() setVisibility(View.VISIBLE); } - public void init(Token tokenItem) + public void init(LifiToken tokenItem) { this.tokenItem = tokenItem; @@ -189,7 +189,7 @@ public void afterTextChanged(Editable editable) }); } - public Token getToken() + public LifiToken getToken() { return this.tokenItem; } @@ -255,7 +255,7 @@ public interface TokenSelectorEventListener /** * Triggered when a new Token is selected. **/ - void onSelectionChanged(Token token); + void onSelectionChanged(LifiToken token); /** * Triggered when the `Max` button is clicked. diff --git a/app/src/test/java/com/alphawallet/app/entity/lifi/ActionTest.java b/app/src/test/java/com/alphawallet/app/entity/lifi/ActionTest.java index eed2ee523c..bfab1868a4 100644 --- a/app/src/test/java/com/alphawallet/app/entity/lifi/ActionTest.java +++ b/app/src/test/java/com/alphawallet/app/entity/lifi/ActionTest.java @@ -11,8 +11,8 @@ public class ActionTest public void should_return_current_price() { Action action = new Action(); - action.fromToken = new Token(); - action.toToken = new Token(); + action.fromToken = new LifiToken(); + action.toToken = new LifiToken(); action.fromToken.priceUSD = "5"; action.fromToken.decimals = 18; action.toToken.priceUSD = "1000"; diff --git a/app/src/test/java/com/alphawallet/app/entity/lifi/TokenTest.java b/app/src/test/java/com/alphawallet/app/entity/lifi/LifiTokenTest.java similarity index 88% rename from app/src/test/java/com/alphawallet/app/entity/lifi/TokenTest.java rename to app/src/test/java/com/alphawallet/app/entity/lifi/LifiTokenTest.java index 6c7d72a6ca..4a91c87654 100644 --- a/app/src/test/java/com/alphawallet/app/entity/lifi/TokenTest.java +++ b/app/src/test/java/com/alphawallet/app/entity/lifi/LifiTokenTest.java @@ -5,12 +5,12 @@ import org.junit.Test; -public class TokenTest +public class LifiTokenTest { @Test public void getFiatValue() { - Token lToken = new Token(); + LifiToken lToken = new LifiToken(); lToken.priceUSD = "6.72"; lToken.balance = "1"; @@ -20,7 +20,7 @@ public void getFiatValue() @Test public void getFiatValue_should_handle_exception() { - Token lToken = new Token(); + LifiToken lToken = new LifiToken(); lToken.priceUSD = "6.72"; lToken.balance = ""; assertThat(lToken.getFiatValue(), equalTo(0.0)); diff --git a/app/src/test/java/com/alphawallet/app/ui/widget/adapter/TokenFilterTest.java b/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java similarity index 77% rename from app/src/test/java/com/alphawallet/app/ui/widget/adapter/TokenFilterTest.java rename to app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java index 84e4513f27..99a8174d09 100644 --- a/app/src/test/java/com/alphawallet/app/ui/widget/adapter/TokenFilterTest.java +++ b/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java @@ -5,8 +5,7 @@ import androidx.annotation.NonNull; -import com.alphawallet.app.entity.lifi.Connection; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import org.junit.Before; import org.junit.Test; @@ -14,14 +13,14 @@ import java.util.ArrayList; import java.util.List; -public class TokenFilterTest +public class LifiTokenFilterTest { private TokenFilter tokenFilter; @Before public void setUp() throws Exception { - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(createToken("Ethereum", "ETH", "1")); list.add(createToken("Solana", "SOL", "2")); list.add(createToken("Binance", "BNB", "3")); @@ -32,7 +31,7 @@ public void setUp() throws Exception @Test public void nameContains() { - List result = tokenFilter.filterBy("an"); + List result = tokenFilter.filterBy("an"); assertThat(result.size(), equalTo(2)); assertThat(result.get(0).name, equalTo("Solana")); assertThat(result.get(1).name, equalTo("Binance")); @@ -41,7 +40,7 @@ public void nameContains() @Test public void nameStartsWith() { - List result = tokenFilter.filterBy("So"); + List result = tokenFilter.filterBy("So"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); } @@ -49,7 +48,7 @@ public void nameStartsWith() @Test public void symbolContains() { - List result = tokenFilter.filterBy("B"); + List result = tokenFilter.filterBy("B"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Binance")); } @@ -57,7 +56,7 @@ public void symbolContains() @Test public void symbolStartsWith() { - List result = tokenFilter.filterBy("S"); + List result = tokenFilter.filterBy("S"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); } @@ -65,7 +64,7 @@ public void symbolStartsWith() @Test public void should_be_case_insensitive() { - List result = tokenFilter.filterBy("s"); + List result = tokenFilter.filterBy("s"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); @@ -77,22 +76,22 @@ public void should_be_case_insensitive() @Test public void should_sort_starts_with_in_front_of_contains() { - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(createToken("Solana", "SOL", "2")); list.add(createToken("WETH", "WETH", "2")); list.add(createToken("Ethereum", "ETH", "1")); tokenFilter = new TokenFilter(list); - List result = tokenFilter.filterBy("eth"); + List result = tokenFilter.filterBy("eth"); assertThat(result.size(), equalTo(2)); assertThat(result.get(0).name, equalTo("Ethereum")); assertThat(result.get(1).name, equalTo("WETH")); } @NonNull - private Token createToken(String name, String symbol, String address) + private LifiToken createToken(String name, String symbol, String address) { - Token e = new Token(); + LifiToken e = new LifiToken(); e.name = name; e.symbol = symbol; e.address = address; diff --git a/app/src/test/java/com/alphawallet/app/util/SwapUtilsTest.java b/app/src/test/java/com/alphawallet/app/util/SwapUtilsTest.java index 4c4452d1c0..a03e9d3ca1 100644 --- a/app/src/test/java/com/alphawallet/app/util/SwapUtilsTest.java +++ b/app/src/test/java/com/alphawallet/app/util/SwapUtilsTest.java @@ -6,8 +6,7 @@ import com.alphawallet.app.entity.lifi.Action; import com.alphawallet.app.entity.lifi.Estimate; import com.alphawallet.app.entity.lifi.GasCost; -import com.alphawallet.app.entity.lifi.Quote; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import org.junit.Test; @@ -21,13 +20,13 @@ public void should_return_formatted_total_gas_fees() ArrayList gasCostList = new ArrayList<>(); GasCost gasCost1 = new GasCost(); gasCost1.amount = "1000000000000000000"; - gasCost1.token = new Token(); + gasCost1.token = new LifiToken(); gasCost1.token.symbol = "ETH"; gasCost1.token.decimals = 18; GasCost gasCost2 = new GasCost(); gasCost2.amount = "2000000000000000000"; - gasCost2.token = new Token(); + gasCost2.token = new LifiToken(); gasCost2.token.symbol = "MATIC"; gasCost2.token.decimals = 18; @@ -42,7 +41,7 @@ public void should_return_formatted_gas_fee() { GasCost gasCost = new GasCost(); gasCost.amount = "1000000000000000000"; - gasCost.token = new Token(); + gasCost.token = new LifiToken(); gasCost.token.symbol = "ETH"; gasCost.token.decimals = 18; @@ -53,7 +52,7 @@ public void should_return_formatted_gas_fee() public void should_return_formatted_minimum_received() { Action action = new Action(); - action.toToken = new Token(); + action.toToken = new LifiToken(); action.toToken.decimals = 6; action.toToken.symbol = "ETH"; @@ -70,8 +69,8 @@ public void should_return_formatted_minimum_received() public void should_return_formatted_current_price() { Action action = new Action(); - action.fromToken = new Token(); - action.toToken = new Token(); + action.fromToken = new LifiToken(); + action.toToken = new LifiToken(); action.fromToken.priceUSD = "2000"; action.fromToken.symbol = "ETH"; action.fromToken.decimals = 18; diff --git a/app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java b/app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java index c6a058005a..9c2afa5b67 100644 --- a/app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java +++ b/app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java @@ -3,8 +3,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; -import com.alphawallet.app.entity.lifi.Connection; -import com.alphawallet.app.entity.lifi.Token; +import com.alphawallet.app.entity.lifi.LifiToken; import org.junit.Test; @@ -16,7 +15,7 @@ public class TokensTest @Test public void sort_token_by_fiat_value_in_DESC() { - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(createToken("Ethereum", "ETH", "0x0", 0)); list.add(createToken("Binance Smart Chain", "BNB", "0x1", 1)); list.add(createToken("Solana", "SOL", "0x2", 2)); @@ -31,7 +30,7 @@ public void sort_token_by_fiat_value_in_DESC() @Test public void sort_tokens_by_name_alphabetically() { - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(createToken("Ethereum", "ETH", "0x0", 0)); list.add(createToken("Binance Smart Chain", "BNB", "0x1", 0)); list.add(createToken("Solana", "SOL", "0x2", 0)); @@ -46,7 +45,7 @@ public void sort_tokens_by_name_alphabetically() @Test public void sort_name_should_return_native_token_first() { - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(createToken("Solana", "SOL", "0x0", 0)); list.add(createToken("Stox", "STX", "0x0000000000000000000000000000000000000000", 0)); list.add(createToken("stETH", "stETH", "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 0)); @@ -61,7 +60,7 @@ public void sort_name_should_return_native_token_first() @Test public void sort_name_should_be_case_insensitive() { - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(createToken("Stox", "STX", "0x0", 0)); list.add(createToken("stETH", "stETH", "0x3", 0)); @@ -71,9 +70,9 @@ public void sort_name_should_be_case_insensitive() assertThat(list.get(1).symbol, equalTo("STX")); } - private Token createToken(String name, String symbol, String address, double fiatEquivalent) + private LifiToken createToken(String name, String symbol, String address, double fiatEquivalent) { - Token lToken = new Token(); + LifiToken lToken = new LifiToken(); lToken.name = name; lToken.symbol = symbol; lToken.address = address; From ffe82255c14e1dd9f3646459a1eddfe1d9aefac9 Mon Sep 17 00:00:00 2001 From: justindg Date: Mon, 24 Apr 2023 17:49:55 -0700 Subject: [PATCH 02/32] Rename event listener --- .../app/ui/widget/adapter/SelectLifiTokenAdapter.java | 4 ++-- .../com/alphawallet/app/widget/SelectLifiTokenDialog.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java index 1098ced035..f66db64187 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java @@ -21,11 +21,11 @@ public class SelectLifiTokenAdapter extends RecyclerView.Adapter { private final List displayData; - private final SelectLifiTokenDialog.SelectLifiTokenDialogEventListener callback; + private final SelectLifiTokenDialog.EventListener callback; private final TokenFilter tokenFilter; private String selectedTokenAddress; - public SelectLifiTokenAdapter(List tokens, SelectLifiTokenDialog.SelectLifiTokenDialogEventListener callback) + public SelectLifiTokenAdapter(List tokens, SelectLifiTokenDialog.EventListener callback) { tokenFilter = new TokenFilter(tokens); this.callback = callback; diff --git a/app/src/main/java/com/alphawallet/app/widget/SelectLifiTokenDialog.java b/app/src/main/java/com/alphawallet/app/widget/SelectLifiTokenDialog.java index ec2ba0365d..80c81fca7b 100644 --- a/app/src/main/java/com/alphawallet/app/widget/SelectLifiTokenDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/SelectLifiTokenDialog.java @@ -56,7 +56,7 @@ public SelectLifiTokenDialog(@NonNull Activity activity) btnClose.setOnClickListener(v -> dismiss()); } - public SelectLifiTokenDialog(List tokenItems, Activity activity, SelectLifiTokenDialogEventListener callback) + public SelectLifiTokenDialog(List tokenItems, Activity activity, EventListener callback) { this(activity); @@ -100,7 +100,7 @@ public void setSelectedToken(String address) adapter.setSelectedToken(address); } - public interface SelectLifiTokenDialogEventListener + public interface EventListener { void onChainSelected(LifiToken tokenItem); } From 4bfd9dd0454176e555361c414438841c8b549739 Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 27 Apr 2023 15:37:53 -0700 Subject: [PATCH 03/32] Initial commit --- .../repository/PreferenceRepositoryType.java | 7 ++ .../SharedPreferenceRepository.java | 20 ++++ .../app/router/SendTokenRouter.java | 14 +++ .../ui/widget/adapter/SelectTokenAdapter.java | 107 ++++++++++++++++++ .../alphawallet/app/util/TokenFilter2.java | 51 +++++++++ .../app/viewmodel/BaseNavigationActivity.java | 7 ++ .../com/alphawallet/app/viewmodel/Tokens.java | 7 +- .../app/widget/SelectTokenDialog.java | 103 +++++++++++++++++ app/src/main/res/drawable/ic_arrow_send.xml | 4 + app/src/main/res/layout/item_select_token.xml | 52 +++++++++ 10 files changed, 369 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java create mode 100644 app/src/main/java/com/alphawallet/app/util/TokenFilter2.java create mode 100644 app/src/main/java/com/alphawallet/app/widget/SelectTokenDialog.java create mode 100644 app/src/main/res/drawable/ic_arrow_send.xml create mode 100644 app/src/main/res/layout/item_select_token.xml diff --git a/app/src/main/java/com/alphawallet/app/repository/PreferenceRepositoryType.java b/app/src/main/java/com/alphawallet/app/repository/PreferenceRepositoryType.java index cd0a4b0095..6cc839b7fc 100644 --- a/app/src/main/java/com/alphawallet/app/repository/PreferenceRepositoryType.java +++ b/app/src/main/java/com/alphawallet/app/repository/PreferenceRepositoryType.java @@ -1,6 +1,9 @@ package com.alphawallet.app.repository; +import android.util.Pair; + import com.alphawallet.app.entity.CurrencyItem; +import com.alphawallet.app.entity.tokens.Token; import java.util.Set; @@ -123,4 +126,8 @@ public interface PreferenceRepositoryType boolean isCrashReportingEnabled(); void setCrashReportingEnabled(boolean isEnabled); + + void setLastSentToken(Token address); + + Pair getLastSentToken(); } diff --git a/app/src/main/java/com/alphawallet/app/repository/SharedPreferenceRepository.java b/app/src/main/java/com/alphawallet/app/repository/SharedPreferenceRepository.java index aab4d5ec49..cdf31c0ead 100644 --- a/app/src/main/java/com/alphawallet/app/repository/SharedPreferenceRepository.java +++ b/app/src/main/java/com/alphawallet/app/repository/SharedPreferenceRepository.java @@ -3,12 +3,14 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; import com.alphawallet.app.C; import com.alphawallet.app.entity.CurrencyItem; +import com.alphawallet.app.entity.tokens.Token; import java.util.HashSet; import java.util.Locale; @@ -49,6 +51,8 @@ public class SharedPreferenceRepository implements PreferenceRepositoryType { private static final String RATE_APP_SHOWN = "rate_us_shown"; private static final String LAUNCH_COUNT = "launch_count"; private static final String NEW_WALLET = "new_wallet_"; + private static final String LAST_SENT_TOKEN_ADDRESS = "last_sent_token_address"; + private static final String LAST_SENT_TOKEN_CHAIN_ID = "last_sent_token_chain_id"; private final SharedPreferences pref; @@ -410,6 +414,22 @@ public void setCrashReportingEnabled(boolean isEnabled) pref.edit().putBoolean(CRASH_REPORTING_KEY, isEnabled).apply(); } + @Override + public void setLastSentToken(Token token) + { + pref.edit().putString(LAST_SENT_TOKEN_ADDRESS, token.tokenInfo.address).apply(); + pref.edit().putLong(LAST_SENT_TOKEN_CHAIN_ID, token.tokenInfo.chainId).apply(); + } + + @Override + public Pair getLastSentToken() + { + return new Pair<>( + pref.getString(LAST_SENT_TOKEN_ADDRESS, ""), + pref.getLong(LAST_SENT_TOKEN_CHAIN_ID, 0) + ); + } + @NonNull private String keyOf(String address) { diff --git a/app/src/main/java/com/alphawallet/app/router/SendTokenRouter.java b/app/src/main/java/com/alphawallet/app/router/SendTokenRouter.java index 43def2fde1..4292521d98 100644 --- a/app/src/main/java/com/alphawallet/app/router/SendTokenRouter.java +++ b/app/src/main/java/com/alphawallet/app/router/SendTokenRouter.java @@ -23,4 +23,18 @@ public void open(Activity context, String address, String symbol, int decimals, intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); context.startActivityForResult(intent, C.COMPLETED_TRANSACTION); } + + // TODO: Check if this could be further refactored + public void open(Activity context, Wallet wallet, Token token) { + Intent intent = new Intent(context, SendActivity.class); + intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, wallet.address); + intent.putExtra(C.EXTRA_ADDRESS, token.getAddress()); + intent.putExtra(C.EXTRA_NETWORKID, token.tokenInfo.chainId); + intent.putExtra(C.EXTRA_SYMBOL, token.getSymbol()); + intent.putExtra(C.EXTRA_DECIMALS, token.tokenInfo.decimals); + intent.putExtra(C.Key.WALLET, wallet); + intent.putExtra(C.EXTRA_AMOUNT, (QRResult)null); + intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + context.startActivityForResult(intent, C.COMPLETED_TRANSACTION); + } } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java new file mode 100644 index 0000000000..97ea39213a --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java @@ -0,0 +1,107 @@ +package com.alphawallet.app.ui.widget.adapter; + +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.alphawallet.app.R; +import com.alphawallet.app.entity.tokens.Token; +import com.alphawallet.app.util.TokenFilter2; +import com.alphawallet.app.widget.SelectTokenDialog; +import com.alphawallet.app.widget.TokenIcon; + +import java.util.ArrayList; +import java.util.List; + +public class SelectTokenAdapter extends RecyclerView.Adapter +{ + private final List displayData; + private final TokenFilter2 tokenFilter; + private final SelectTokenDialog.OnTokenClickListener listener; + + public SelectTokenAdapter(List tokens, SelectTokenDialog.OnTokenClickListener listener) + { + this.listener = listener; + tokenFilter = new TokenFilter2(tokens); + displayData = new ArrayList<>(); + displayData.addAll(tokens); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) + { + View itemView = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_select_token, parent, false); + return new ViewHolder(itemView); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) + { + holder.setIsRecyclable(false); + Token token = displayData.get(position); + if (token != null) + { + holder.name.setText(token.tokenInfo.name); + holder.tokenIcon.bindData(token); + + String balance = token.getStringBalanceForUI(token.tokenInfo.decimals); + if (!TextUtils.isEmpty(balance)) + { + holder.balance.setText(balance); + holder.balance.append(" "); + } + else + { + holder.balance.setText("0 "); + } + + holder.balance.append(token.getSymbol()); + + holder.itemLayout.setOnClickListener(v -> listener.onTokenClicked(token)); + } + } + + public void filter(String keyword) + { + updateList(tokenFilter.filterBy(keyword)); + } + + public void updateList(List filteredList) + { + displayData.clear(); + displayData.addAll(filteredList); + notifyItemRangeChanged(0, filteredList.size()); + } + + @Override + public int getItemCount() + { + return displayData.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder + { + TextView name; + TextView balance; + View itemLayout; + TokenIcon tokenIcon; + + ViewHolder(View view) + { + super(view); + name = view.findViewById(R.id.name); + balance = view.findViewById(R.id.balance); + itemLayout = view.findViewById(R.id.layout_list_item); + tokenIcon = view.findViewById(R.id.token_icon); + } + } + + +} diff --git a/app/src/main/java/com/alphawallet/app/util/TokenFilter2.java b/app/src/main/java/com/alphawallet/app/util/TokenFilter2.java new file mode 100644 index 0000000000..afd6f3ceaa --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/util/TokenFilter2.java @@ -0,0 +1,51 @@ +package com.alphawallet.app.util; + +import androidx.annotation.NonNull; + +import com.alphawallet.app.entity.tokens.Token; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class TokenFilter2 +{ + private final List tokens; + + public TokenFilter2(List tokens) + { + this.tokens = tokens; + } + + public List filterBy(String keyword) + { + String lowerCaseKeyword = lowerCase(keyword); + + List result = new ArrayList<>(); + for (Token t : this.tokens) + { + String name = lowerCase(t.getFullName()); + String symbol = lowerCase(t.getSymbol()); + + if (name.startsWith(lowerCaseKeyword) || symbol.startsWith(lowerCaseKeyword)) + { + result.add(0, t); + } + else if (name.contains(lowerCaseKeyword) || symbol.contains(lowerCaseKeyword)) + { + if (!result.contains(t)) + { + result.add(t); + } + } + } + + return result; + } + + @NonNull + private String lowerCase(String name) + { + return name.toLowerCase(Locale.ENGLISH); + } +} diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java b/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java index 0b215c0e59..f5692973af 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java @@ -1,10 +1,12 @@ package com.alphawallet.app.viewmodel; +import android.content.Intent; import android.view.View; import com.alphawallet.app.R; import com.alphawallet.app.entity.WalletPage; import com.alphawallet.app.ui.BaseActivity; +import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.widget.AWalletBottomNavigationView; public class BaseNavigationActivity extends BaseActivity implements AWalletBottomNavigationView.OnBottomNavigationItemSelectedListener { @@ -13,6 +15,11 @@ public class BaseNavigationActivity extends BaseActivity implements AWalletBotto protected void initBottomNavigation() { nav = findViewById(R.id.nav); nav.setListener(this); + nav.setSendButtonListener(v -> { + Intent intent = new Intent(this, SendActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + }); } protected void selectNavigationItem(WalletPage position) { diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java b/app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java index 07689b8646..e5c00da294 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java @@ -1,6 +1,7 @@ package com.alphawallet.app.viewmodel; -import com.alphawallet.app.entity.lifi.Token; + +import com.alphawallet.app.entity.lifi.LifiToken; import java.math.BigDecimal; import java.util.Collections; @@ -8,7 +9,7 @@ public class Tokens { - public static void sortValue(List tokenItems) + public static void sortValue(List tokenItems) { Collections.sort(tokenItems, (l, r) -> { if (l.isNativeToken()) @@ -28,7 +29,7 @@ else if (r.isNativeToken()) }); } - public static void sortName(List tokenItems) + public static void sortName(List tokenItems) { Collections.sort(tokenItems, (l, r) -> { if (l.isNativeToken()) diff --git a/app/src/main/java/com/alphawallet/app/widget/SelectTokenDialog.java b/app/src/main/java/com/alphawallet/app/widget/SelectTokenDialog.java new file mode 100644 index 0000000000..80ba5a6147 --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/widget/SelectTokenDialog.java @@ -0,0 +1,103 @@ +package com.alphawallet.app.widget; + +import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED; + +import android.app.Activity; +import android.content.res.Resources; +import android.os.Handler; +import android.os.Looper; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.alphawallet.app.R; +import com.alphawallet.app.entity.tokens.Token; +import com.alphawallet.app.ui.widget.adapter.SelectTokenAdapter; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; + +import java.util.List; + +public class SelectTokenDialog extends BottomSheetDialog +{ + private final Handler handler = new Handler(Looper.getMainLooper()); + private RecyclerView tokenList; + private SelectTokenAdapter adapter; + private LinearLayout searchLayout; + private EditText search; + private TextView noResultsText; + + public SelectTokenDialog(@NonNull Activity activity) + { + super(activity); + View view = View.inflate(getContext(), R.layout.dialog_select_token, null); + setContentView(view); + + setOnShowListener(dialogInterface -> { + view.setMinimumHeight(Resources.getSystem().getDisplayMetrics().heightPixels); + BottomSheetBehavior behavior = BottomSheetBehavior.from((View) view.getParent()); + behavior.setState(STATE_EXPANDED); + behavior.setSkipCollapsed(true); + }); + + tokenList = view.findViewById(R.id.token_list); + search = view.findViewById(R.id.edit_search); + searchLayout = view.findViewById(R.id.layout_search_tokens); + noResultsText = view.findViewById(R.id.no_results); + ImageView btnClose = view.findViewById(R.id.image_close); + btnClose.setOnClickListener(v -> dismiss()); + search.requestFocus(); + } + + public SelectTokenDialog(List tokenItems, Activity activity, OnTokenClickListener listener) + { + this(activity); + + noResultsText.setVisibility(tokenItems.size() > 0 ? View.GONE : View.VISIBLE); + + adapter = new SelectTokenAdapter(tokenItems, listener); + + tokenList.setLayoutManager(new LinearLayoutManager(getContext())); + tokenList.setAdapter(adapter); + + searchLayout.setOnClickListener(v -> { + }); + + search.addTextChangedListener(new TextWatcher() + { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) + { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) + { + } + + @Override + public void afterTextChanged(final Editable searchFilter) + { + handler.postDelayed(() -> { + if (adapter != null) + { + adapter.filter(searchFilter.toString()); + } + }, 200); + } + }); + } + + public interface OnTokenClickListener + { + void onTokenClicked(Token token); + } +} diff --git a/app/src/main/res/drawable/ic_arrow_send.xml b/app/src/main/res/drawable/ic_arrow_send.xml new file mode 100644 index 0000000000..b9cfad770c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_send.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/layout/item_select_token.xml b/app/src/main/res/layout/item_select_token.xml new file mode 100644 index 0000000000..598ee2a953 --- /dev/null +++ b/app/src/main/res/layout/item_select_token.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + \ No newline at end of file From 76e5812594ec488944cf8449171f861dd7c0da8d Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 27 Apr 2023 15:48:19 -0700 Subject: [PATCH 04/32] Cleanup Bottom Navigation --- .../widget/AWalletBottomNavigationView.java | 23 +++++++------------ app/src/main/res/layout/activity_home.xml | 2 +- .../res/layout/layout_bottom_navigation.xml | 22 ++++++++++++++---- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/typography.xml | 4 ++++ 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/widget/AWalletBottomNavigationView.java b/app/src/main/java/com/alphawallet/app/widget/AWalletBottomNavigationView.java index 3f2d07a95e..2eccf3e6bb 100644 --- a/app/src/main/java/com/alphawallet/app/widget/AWalletBottomNavigationView.java +++ b/app/src/main/java/com/alphawallet/app/widget/AWalletBottomNavigationView.java @@ -6,7 +6,6 @@ import static com.alphawallet.app.entity.WalletPage.WALLET; import android.content.Context; -import android.graphics.Typeface; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; @@ -14,7 +13,6 @@ import android.widget.TextView; import androidx.annotation.Nullable; -import androidx.core.content.res.ResourcesCompat; import com.alphawallet.app.R; import com.alphawallet.app.entity.WalletPage; @@ -27,10 +25,10 @@ public class AWalletBottomNavigationView extends LinearLayout private final TextView walletLabel; private final TextView settingsBadge; private final TextView settingsLabel; + + private final TextView sendButton; private final RelativeLayout settingsTab; private final TextView activityLabel; - private final Typeface regularTypeface; - private final Typeface semiboldTypeface; private final ArrayList settingsBadgeKeys = new ArrayList<>(); private OnBottomNavigationItemSelectedListener listener; private WalletPage selectedItem; @@ -41,6 +39,7 @@ public AWalletBottomNavigationView(Context context, @Nullable AttributeSet attrs inflate(context, R.layout.layout_bottom_navigation, this); walletLabel = findViewById(R.id.nav_wallet_text); activityLabel = findViewById(R.id.nav_activity_text); + sendButton = findViewById(R.id.nav_send); dappBrowserLabel = findViewById(R.id.nav_browser_text); settingsTab = findViewById(R.id.settings_tab); settingsLabel = findViewById(R.id.nav_settings_text); @@ -51,13 +50,15 @@ public AWalletBottomNavigationView(Context context, @Nullable AttributeSet attrs dappBrowserLabel.setOnClickListener(v -> selectItem(DAPP_BROWSER)); settingsTab.setOnClickListener(v -> selectItem(SETTINGS)); - regularTypeface = ResourcesCompat.getFont(getContext(), R.font.font_regular); - semiboldTypeface = ResourcesCompat.getFont(getContext(), R.font.font_semibold); - // set wallet fragment selected on start setSelectedItem(WALLET); } + public void setSendButtonListener(View.OnClickListener listener) + { + sendButton.setOnClickListener(listener); + } + public void setListener(OnBottomNavigationItemSelectedListener listener) { this.listener = listener; @@ -81,19 +82,15 @@ public void setSelectedItem(WalletPage index) { case DAPP_BROWSER: dappBrowserLabel.setSelected(true); - dappBrowserLabel.setTypeface(semiboldTypeface); break; case WALLET: walletLabel.setSelected(true); - walletLabel.setTypeface(semiboldTypeface); break; case SETTINGS: settingsLabel.setSelected(true); - settingsLabel.setTypeface(semiboldTypeface); break; case ACTIVITY: activityLabel.setSelected(true); - activityLabel.setTypeface(semiboldTypeface); break; } } @@ -101,13 +98,9 @@ public void setSelectedItem(WalletPage index) private void deselectAll() { dappBrowserLabel.setSelected(false); - dappBrowserLabel.setTypeface(regularTypeface); walletLabel.setSelected(false); - walletLabel.setTypeface(regularTypeface); settingsLabel.setSelected(false); - settingsLabel.setTypeface(regularTypeface); activityLabel.setSelected(false); - activityLabel.setTypeface(regularTypeface); } public void setSettingsBadgeCount(int count) diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index db9b164d78..7421fe3bc8 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -18,7 +18,7 @@ diff --git a/app/src/main/res/layout/layout_bottom_navigation.xml b/app/src/main/res/layout/layout_bottom_navigation.xml index 5cdf23e64a..3ee1189626 100644 --- a/app/src/main/res/layout/layout_bottom_navigation.xml +++ b/app/src/main/res/layout/layout_bottom_navigation.xml @@ -3,7 +3,7 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="?android:attr/actionBarSize" + android:layout_height="@dimen/bottom_navigation_bar" android:background="?colorSurface" android:orientation="vertical"> @@ -18,7 +18,7 @@ + + 4dp 60dp + 60dp 12dp 60dp 5dp diff --git a/app/src/main/res/values/typography.xml b/app/src/main/res/values/typography.xml index fc9127f41e..ed7ac620eb 100644 --- a/app/src/main/res/values/typography.xml +++ b/app/src/main/res/values/typography.xml @@ -90,6 +90,10 @@ @dimen/sp12 + + From d669d351dd9e05311479d285f7160c9d51f607b3 Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:08:07 -0700 Subject: [PATCH 05/32] Create new activity for handling Transfer Requests --- app/src/main/AndroidManifest.xml | 4 + .../app/router/SendTokenRouter.java | 42 +- .../app/router/TransferRequestRouter.java | 20 + .../alphawallet/app/ui/AddTokenActivity.java | 4 +- .../app/ui/DappBrowserFragment.java | 2 +- .../app/ui/Erc20DetailActivity.java | 2 +- .../app/ui/TransferRequestActivity.java | 623 ++++++++++++++++++ .../app/viewmodel/BaseNavigationActivity.java | 5 +- .../app/viewmodel/DappBrowserViewModel.java | 19 +- .../app/viewmodel/Erc20DetailViewModel.java | 5 +- .../app/viewmodel/NFTInfoViewModel.java | 9 - .../app/viewmodel/NFTViewModel.java | 9 - .../res/layout/activity_transfer_request.xml | 74 +++ .../app/router/CoinbasePayRouterTest.java | 2 - 14 files changed, 753 insertions(+), 67 deletions(-) create mode 100644 app/src/main/java/com/alphawallet/app/router/TransferRequestRouter.java create mode 100644 app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java create mode 100644 app/src/main/res/layout/activity_transfer_request.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 035fd07852..be69dfb1dc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -362,6 +362,10 @@ android:name=".ui.CrashReportSettingsActivity" android:label="@string/settings_title_crash_reporting" /> + + selectio @Override public void showSend() { - viewModel.showSendToken(this, wallet, token); + viewModel.showSendToken(this, token); } @Override diff --git a/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java b/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java new file mode 100644 index 0000000000..6b053993ff --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java @@ -0,0 +1,623 @@ +package com.alphawallet.app.ui; + +import static com.alphawallet.app.widget.AWalletAlertDialog.ERROR; +import static com.alphawallet.app.widget.AWalletAlertDialog.WARNING; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.view.MenuItem; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; +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.EIP681Type; +import com.alphawallet.app.entity.ErrorEnvelope; +import com.alphawallet.app.entity.GasEstimate; +import com.alphawallet.app.entity.Operation; +import com.alphawallet.app.entity.QRResult; +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.tokens.Token; +import com.alphawallet.app.service.GasService; +import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; +import com.alphawallet.app.ui.widget.entity.AddressReadyCallback; +import com.alphawallet.app.ui.widget.entity.AmountReadyCallback; +import com.alphawallet.app.util.Utils; +import com.alphawallet.app.viewmodel.SendViewModel; +import com.alphawallet.app.web3.entity.Address; +import com.alphawallet.app.web3.entity.Web3Transaction; +import com.alphawallet.app.widget.AWalletAlertDialog; +import com.alphawallet.app.widget.ActionSheetDialog; +import com.alphawallet.app.widget.FunctionButtonBar; +import com.alphawallet.app.widget.InputAddress; +import com.alphawallet.app.widget.InputAmount; +import com.alphawallet.app.widget.SignTransactionDialog; +import com.alphawallet.hardware.SignatureFromKey; +import com.alphawallet.token.tools.Convert; +import com.alphawallet.token.tools.Numeric; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.Collections; + +import dagger.hilt.android.AndroidEntryPoint; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; + +@AndroidEntryPoint +public class TransferRequestActivity extends BaseActivity implements + AmountReadyCallback, + StandardFunctionInterface, + AddressReadyCallback, + ActionSheetCallback +{ + private static final BigDecimal NEGATIVE = BigDecimal.ZERO.subtract(BigDecimal.ONE); + private final Handler handler = new Handler(); + private SendViewModel viewModel; + private Wallet wallet; + private Token token; + private AWalletAlertDialog dialog; + private AWalletAlertDialog progressDialog; + private QRResult result; + private InputAmount amountInput; + private InputAddress addressInput; + private FunctionButtonBar functionBar; + private String sendAddress = null; + private String ensAddress; + private BigDecimal sendAmount = NEGATIVE; + private BigDecimal sendGasPrice = BigDecimal.ZERO; + private ActionSheetDialog confirmationDialog; + private final ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + result -> confirmationDialog.setCurrentGasIndex(result)); + @Nullable + private Disposable calcGasCost; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_transfer_request); + + toolbar(); + + setTitle(R.string.empty); + + initViews(); + + initViewModel(); + + result = getIntent().getParcelableExtra(C.EXTRA_AMOUNT); + + evaluateQrResult(result); + } + + private void initViews() + { + amountInput = findViewById(R.id.input_amount); + addressInput = findViewById(R.id.input_address); + functionBar = findViewById(R.id.layoutButtons); + + addressInput.setAddressCallback(this); + amountInput.hideCaret(); + + //TODO Send: set uneditable amount and address input. + amountInput.setEnabled(false); +// addressInput.setEnabled(false); + addressInput.setEditable(false); + addressInput.showControls(false); + + progressDialog = new AWalletAlertDialog(this); + progressDialog.setTitle(R.string.searching_for_token); + progressDialog.setIcon(AWalletAlertDialog.NONE); + progressDialog.setProgressMode(); + progressDialog.setCancelable(false); + } + + private void initViewModel() + { + viewModel = new ViewModelProvider(this).get(SendViewModel.class); + viewModel.wallet().observe(this, this::onWallet); + viewModel.finalisedToken().observe(this, this::onFinalisedToken); + viewModel.transactionFinalised().observe(this, this::txWritten); + viewModel.transactionError().observe(this, this::txError); + viewModel.error().observe(this, this::onError); + viewModel.progress().observe(this, this::showProgress); + } + + private void evaluateQrResult(QRResult r) + { + if (r != null) + { + this.result = r; + viewModel.prepare(); + } + else + { + displayScanError(); + finish(); + } + } + + private void onWallet(Wallet wallet) + { + this.wallet = wallet; + + if (!isValidChain(result.chainId)) + { + finish(); + } + else if (result.type == EIP681Type.PAYMENT) + { + token = viewModel.getToken(result.chainId, wallet.address); + } + else if (result.type == EIP681Type.TRANSFER) + { + token = viewModel.getToken(result.chainId, result.getAddress()); + } + + if (token != null) + { + evaluateToken(token); + } + else + { + Timber.d("You don't have this token, attempt to fetch"); + showProgress(true); + viewModel.fetchToken(result.chainId, result.getAddress(), wallet.address); + } + } + + private void evaluateToken(Token token) + { + if (token != null && token.balance.equals(BigDecimal.ZERO)) + { + displayToast("Wallet does not have requested token"); + showProgress(false); + finish(); + } + else if (token != null && token.isERC20()) + { + setupTokenContent(token); + addressInput.setAddress(result.functionToAddress); + amountInput.setAmount(result.tokenAmount.toString()); + amountInput.getInputAmount(); + } + else if (token != null && token.isEthereum()) + { + setupTokenContent(token); + addressInput.setAddress(result.getAddress()); + amountInput.setAmount(Convert.getConvertedValue(new BigDecimal(result.weiValue), Convert.Unit.ETHER.getFactor())); + amountInput.getInputAmount(); + } + else // TODO: Handle NFT + { + displayToast("NFTs not supported yet."); + finish(); + } + } + + private boolean isValidChain(long chainId) + { + if (viewModel.getNetworkInfo(chainId) == null) + { + displayToast(getString(R.string.chain_not_support, String.valueOf(chainId))); + return false; + } + if (!viewModel.isNetworkEnabled(chainId)) + { + displayToast("Network not enabled"); + return false; + } + return true; + } + + private void onFinalisedToken(Token token) + { + evaluateToken(token); + } + +// private void onTokens(List tokens) +// { +// // Filter tokens here. +// selectTokenDialog = new SelectTokenDialog(tokens, this, this); +// selectTokenDialog.show(); +// } + + private void setupTokenContent(Token token) + { + this.token = token; + amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); + addressInput.setChainOverrideForWalletConnect(token.tokenInfo.chainId); + + setTitle(getString(R.string.action_send_tkn, token.getSymbol())); + + functionBar.revealButtons(); + functionBar.setupFunctions(this, new ArrayList<>(Collections.singletonList(R.string.action_next))); + viewModel.startGasCycle(token.tokenInfo.chainId); + } + + private void onError(ErrorEnvelope errorEnvelope) + { + displayToast(errorEnvelope.message); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + if (item.getItemId() == android.R.id.home) + { + onBackPressed(); + } + return false; + } + + @Override + public void onBackPressed() + { + finish(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + { + Operation taskCode = null; + if (requestCode >= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS && requestCode <= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS + 10) + { + taskCode = Operation.values()[requestCode - SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS]; + requestCode = SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS; + } + if (requestCode >= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS && requestCode <= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS + 10) + { + if (confirmationDialog != null && confirmationDialog.isShowing()) + confirmationDialog.completeSignRequest(resultCode == RESULT_OK); + } + else + { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + 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 displayScanError() + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(AWalletAlertDialog.ERROR); + dialog.setTitle(R.string.toast_qr_code_no_address); + dialog.setButtonText(R.string.dialog_cancel_back); + dialog.setButtonListener(v -> dialog.dismiss()); + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + } + + private void displayError(int titleId, String message) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(AWalletAlertDialog.ERROR); + dialog.setTitle(titleId); + dialog.setMessage(message); + dialog.setButtonText(R.string.dialog_ok); + dialog.setButtonListener(v -> dialog.dismiss()); + dialog.show(); + } + + @Override + protected void onDestroy() + { + if (dialog != null && dialog.isShowing()) + { + dialog.dismiss(); + } + super.onDestroy(); + if (viewModel != null) viewModel.onDestroy(); + if (handler != null) handler.removeCallbacksAndMessages(null); + if (amountInput != null) amountInput.onDestroy(); + if (confirmationDialog != null) confirmationDialog.onDestroy(); + } + + @Override + public void amountReady(BigDecimal value, BigDecimal gasPrice) + { + if (isBalanceSufficient(value)) + { + sendAmount = value; + sendGasPrice = gasPrice; + calculateTransactionCost(); + } + else + { + sendAmount = NEGATIVE; + amountInput.showError(true, 0); + addressInput.stopNameCheck(); + } + } + + private boolean isBalanceSufficient(BigDecimal value) + { + return (token.isEthereum() && token.balance.subtract(value).compareTo(BigDecimal.ZERO) > 0) // if sending base ethereum then check we have more than just the value + || (token.getBalanceRaw().subtract(value).compareTo(BigDecimal.ZERO) >= 0); + } + + @Override + public void handleClick(String action, int actionId) + { + if (actionId == R.string.action_next) + { + amountInput.getInputAmount(); + addressInput.getAddress(); + } + } + + @Override + public void addressReady(String address, String ensName) + { + sendAddress = address; + ensAddress = ensName; + if (!Utils.isAddressValid(address)) + { + //show address error + addressInput.setError(getString(R.string.error_invalid_address)); + } + else + { + calculateTransactionCost(); + } + } + + private void calculateTransactionCost() + { + if ((calcGasCost != null && !calcGasCost.isDisposed()) || + (confirmationDialog != null && confirmationDialog.isShowing())) return; + + if (sendAmount.compareTo(NEGATIVE) > 0 && Utils.isAddressValid(sendAddress)) + { + final String txSendAddress = sendAddress; + sendAddress = null; + //either sending base chain or ERC20 tokens. + final byte[] transactionBytes = viewModel.getTransactionBytes(token, txSendAddress, sendAmount); + + final String txDestAddress = token.isEthereum() ? txSendAddress : token.getAddress(); //either another address, or ERC20 Token address + + calculateEstimateDialog(); + //form payload and calculate tx cost + calcGasCost = viewModel.calculateGasEstimate(wallet, transactionBytes, token.tokenInfo.chainId, txDestAddress, BigDecimal.ZERO) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(estimate -> checkConfirm(estimate, transactionBytes, txDestAddress, txSendAddress), + error -> handleError(error, transactionBytes, token.getAddress(), txSendAddress)); + } + } + + private void handleError(Throwable throwable, final byte[] transactionBytes, final String txSendAddress, final String resolvedAddress) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + displayErrorMessage(throwable.getMessage()); + } + + private void checkConfirm(GasEstimate estimate, final byte[] transactionBytes, final String txSendAddress, final String resolvedAddress) + { + BigInteger ethValue = token.isEthereum() ? sendAmount.toBigInteger() : BigInteger.ZERO; + long leafCode = amountInput.isSendAll() ? -2 : -1; + Web3Transaction w3tx = new Web3Transaction( + new Address(txSendAddress), + token.isEthereum() ? null : new Address(token.getAddress()), + new Address(wallet.address), + ethValue, + sendGasPrice.toBigInteger(), + estimate.getValue(), + -1, + Numeric.toHexString(transactionBytes), + leafCode); + + if (estimate.hasError() || estimate.getValue().equals(BigInteger.ZERO)) + { + estimateError(estimate, w3tx, transactionBytes, txSendAddress, resolvedAddress); + } + else + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + confirmationDialog = new ActionSheetDialog(this, w3tx, token, ensAddress, + resolvedAddress, viewModel.getTokenService(), this); + confirmationDialog.setCanceledOnTouchOutside(false); + confirmationDialog.show(); + sendAmount = NEGATIVE; + } + } + + @Override + public void getAuthorisation(SignAuthenticationCallback callback) + { + viewModel.getAuthentication(this, wallet, callback); + } + + @Override + public void sendTransaction(Web3Transaction finalTx) + { + viewModel.requestSignature(finalTx, wallet, token.tokenInfo.chainId); + } + + @Override + public void completeSendTransaction(Web3Transaction tx, SignatureFromKey signature) + { + viewModel.sendTransaction(wallet, token.tokenInfo.chainId, tx, signature); + } + + @Override + public void dismissed(String txHash, long callbackId, boolean actionCompleted) + { + if (!TextUtils.isEmpty(txHash)) + { + Intent intent = new Intent(); + intent.putExtra(C.EXTRA_TXHASH, txHash); + setResult(RESULT_OK, intent); + + finish(); + } + } + + @Override + public ActivityResultLauncher gasSelectLauncher() + { + return getGasSettings; + } + + @Override + public void notifyConfirm(String mode) + { + AnalyticsProperties props = new AnalyticsProperties(); + props.put(Analytics.PROPS_ACTION_SHEET_MODE, mode); + viewModel.track(Analytics.Action.ACTION_SHEET_COMPLETED, props); + } + + @Override + public WalletType getWalletType() + { + return wallet.type; + } + + private void txWritten(TransactionReturn txData) + { + confirmationDialog.transactionWritten(txData.hash); + viewModel.setLastSentToken(token); + } + + private void txError(TransactionReturn txError) + { + Timber.e(txError.throwable); + if (txError.throwable instanceof SocketTimeoutException) + { + showTxnTimeoutDialog(); + } + else + { + showTxnErrorDialog(txError.throwable); + } + confirmationDialog.dismiss(); + } + + private void estimateError(GasEstimate estimate, final Web3Transaction w3tx, final byte[] transactionBytes, final String txSendAddress, final String resolvedAddress) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(WARNING); + dialog.setTitle(estimate.hasError() ? + R.string.dialog_title_gas_estimation_failed : + R.string.confirm_transaction + ); + String message = estimate.hasError() ? + getString(R.string.dialog_message_gas_estimation_failed, estimate.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 -> { + BigInteger gasEstimate = GasService.getDefaultGasLimit(token, w3tx); + checkConfirm(new GasEstimate(gasEstimate), transactionBytes, txSendAddress, resolvedAddress); + }); + + dialog.setSecondaryButtonListener(v -> { + dialog.dismiss(); + }); + + dialog.show(); + } + + void showTxnErrorDialog(Throwable t) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(ERROR); + dialog.setTitle(R.string.error_transaction_failed); + dialog.setMessage(t.getMessage()); + dialog.setButtonText(R.string.button_ok); + dialog.setButtonListener(v -> { + dialog.dismiss(); + }); + dialog.show(); + } + + void showTxnTimeoutDialog() + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(WARNING); + dialog.setTitle(R.string.error_transaction_timeout); + dialog.setMessage(R.string.message_transaction_timeout); + dialog.setButton(R.string.ok, v -> { + dialog.dismiss(); + }); + dialog.show(); + } + +// private void showChainChangeDialog(long chainId) +// { +// if (dialog != null && dialog.isShowing()) dialog.dismiss(); +// +// token = viewModel.getToken(chainId, wallet.address); +// +// dialog = new AWalletAlertDialog(this); +// dialog.setIcon(AWalletAlertDialog.WARNING); +// dialog.setTitle(R.string.change_chain_request); +// dialog.setMessage(R.string.change_chain_message); +// dialog.setButtonText(R.string.dialog_ok); +// dialog.setButtonListener(v -> { +// //we should change the chain. +// token = viewModel.getToken(chainId, token.getAddress()); +// amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); +// dialog.dismiss(); +// validateEIP681Request(currentResult, false); +// }); +// dialog.setSecondaryButtonText(R.string.action_cancel); +// dialog.setSecondaryButtonListener(v -> { +// dialog.dismiss(); +// //proceed without changing the chain +// currentResult.chainId = token.tokenInfo.chainId; +// validateEIP681Request(currentResult, false); +// }); +// dialog.show(); +// } + + private void showProgress(Boolean showProgress) + { + if (progressDialog != null) + { + if (showProgress) + { + progressDialog.show(); + } + else + { + progressDialog.dismiss(); + } + } + } +} diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java b/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java index f5692973af..0a67274150 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java @@ -5,6 +5,7 @@ import com.alphawallet.app.R; import com.alphawallet.app.entity.WalletPage; +import com.alphawallet.app.router.SendTokenRouter; import com.alphawallet.app.ui.BaseActivity; import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.widget.AWalletBottomNavigationView; @@ -16,9 +17,7 @@ protected void initBottomNavigation() { nav = findViewById(R.id.nav); nav.setListener(this); nav.setSendButtonListener(v -> { - Intent intent = new Intent(this, SendActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); + new SendTokenRouter().open(this); }); } diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java index a386d0a51d..9fb42649db 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java @@ -31,6 +31,7 @@ import com.alphawallet.app.interact.CreateTransactionInteract; import com.alphawallet.app.interact.GenericWalletInteract; import com.alphawallet.app.repository.EthereumNetworkRepositoryType; +import com.alphawallet.app.router.TransferRequestRouter; import com.alphawallet.app.service.AnalyticsServiceType; import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.GasService; @@ -42,7 +43,6 @@ import com.alphawallet.app.ui.ImportTokenActivity; import com.alphawallet.app.ui.MyAddressActivity; import com.alphawallet.app.ui.QRScanning.QRScannerActivity; -import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.util.DappBrowserUtils; @@ -267,22 +267,9 @@ public void failedAuthentication(Operation signData) keyService.failedAuthentication(signData); } - public void showSend(Context ctx, QRResult result) + public void showSend(Activity ctx, QRResult result) { - Intent intent = new Intent(ctx, SendActivity.class); - boolean sendingTokens = (result.getFunction() != null && result.getFunction().length() > 0); - String address = defaultWallet.getValue().address; - int decimals = 18; - - intent.putExtra(C.EXTRA_SENDING_TOKENS, sendingTokens); - intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, address); - intent.putExtra(C.EXTRA_NETWORKID, result.chainId); - intent.putExtra(C.EXTRA_SYMBOL, ethereumNetworkRepository.getNetworkByChain(result.chainId).symbol); - intent.putExtra(C.EXTRA_DECIMALS, decimals); - intent.putExtra(C.Key.WALLET, defaultWallet.getValue()); - intent.putExtra(C.EXTRA_AMOUNT, result); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - ctx.startActivity(intent); + new TransferRequestRouter().open(ctx, result); } public void requestSignature(Web3Transaction finalTx, Wallet wallet, long chainId) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java index 1a7fe54bd7..a7bff4ff4d 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java @@ -101,12 +101,11 @@ public AssetDefinitionService getAssetDefinitionService() return this.assetDefinitionService; } - public void showSendToken(Activity act, Wallet wallet, Token token) + public void showSendToken(Activity act, Token token) { if (token != null) { - new SendTokenRouter().open(act, wallet.address, token.getSymbol(), token.tokenInfo.decimals, - wallet, token, token.tokenInfo.chainId); + new SendTokenRouter().open(act, token); } } diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/NFTInfoViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/NFTInfoViewModel.java index 3d6ad92f79..85f07aa211 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/NFTInfoViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/NFTInfoViewModel.java @@ -65,15 +65,6 @@ public AssetDefinitionService getAssetDefinitionService() return this.assetDefinitionService; } - public void showSendToken(Activity act, Wallet wallet, Token token) - { - if (token != null) - { - new SendTokenRouter().open(act, wallet.address, token.getSymbol(), token.tokenInfo.decimals, - wallet, token, token.tokenInfo.chainId); - } - } - public void checkTokenScriptValidity(Token token) { disposable = assetDefinitionService.getSignatureData(token.tokenInfo.chainId, token.tokenInfo.address) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/NFTViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/NFTViewModel.java index 7d4f1095d2..76a7606729 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/NFTViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/NFTViewModel.java @@ -96,15 +96,6 @@ public AssetDefinitionService getAssetDefinitionService() return this.assetDefinitionService; } - public void showSendToken(Activity act, Wallet wallet, Token token) - { - if (token != null) - { - new SendTokenRouter().open(act, wallet.address, token.getSymbol(), token.tokenInfo.decimals, - wallet, token, token.tokenInfo.chainId); - } - } - public Realm getRealmInstance(Wallet wallet) { return tokensService.getRealmInstance(wallet); diff --git a/app/src/main/res/layout/activity_transfer_request.xml b/app/src/main/res/layout/activity_transfer_request.xml new file mode 100644 index 0000000000..809b2362a4 --- /dev/null +++ b/app/src/main/res/layout/activity_transfer_request.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/test/java/com/alphawallet/app/router/CoinbasePayRouterTest.java b/app/src/test/java/com/alphawallet/app/router/CoinbasePayRouterTest.java index 38235a88a7..ba321a6ac6 100644 --- a/app/src/test/java/com/alphawallet/app/router/CoinbasePayRouterTest.java +++ b/app/src/test/java/com/alphawallet/app/router/CoinbasePayRouterTest.java @@ -6,11 +6,9 @@ import android.content.Intent; -import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.alphawallet.app.ui.CoinbasePayActivity; -import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.ui.SplashActivity; import com.alphawallet.shadows.ShadowApp; import com.alphawallet.shadows.ShadowKeyProviderFactory; From d55ff4fbdf9c0a3f998caddc591b57914194a180 Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:08:49 -0700 Subject: [PATCH 06/32] Use transfer request router --- .../app/viewmodel/AddTokenViewModel.java | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/AddTokenViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/AddTokenViewModel.java index b821d5b5be..3cd9c3d09c 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/AddTokenViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/AddTokenViewModel.java @@ -1,5 +1,6 @@ package com.alphawallet.app.viewmodel; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Handler; @@ -20,10 +21,10 @@ import com.alphawallet.app.interact.FetchTransactionsInteract; import com.alphawallet.app.interact.GenericWalletInteract; import com.alphawallet.app.repository.EthereumNetworkRepositoryType; +import com.alphawallet.app.router.TransferRequestRouter; import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.TokensService; import com.alphawallet.app.ui.ImportTokenActivity; -import com.alphawallet.app.ui.SendActivity; import java.math.BigDecimal; import java.util.ArrayList; @@ -208,28 +209,9 @@ public void prepare() findWallet(); } - public void showSend(Context ctx, QRResult result, Token token) + public void showSend(Activity ctx, QRResult result) { - Intent intent = new Intent(ctx, SendActivity.class); - boolean sendingTokens = (result.getFunction() != null && result.getFunction().length() > 0); - String address = wallet.getValue().address; - int decimals = 18; - - if (sendingTokens) - { - address = result.getAddress(); - decimals = token.tokenInfo.decimals; - } - - intent.putExtra(C.EXTRA_SENDING_TOKENS, sendingTokens); - intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, address); - intent.putExtra(C.EXTRA_NETWORKID, token.tokenInfo.chainId); - intent.putExtra(C.EXTRA_SYMBOL, ethereumNetworkRepository.getNetworkByChain(result.chainId).symbol); - intent.putExtra(C.EXTRA_DECIMALS, decimals); - intent.putExtra(C.Key.WALLET, wallet.getValue()); - intent.putExtra(C.EXTRA_AMOUNT, result); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - ctx.startActivity(intent); + new TransferRequestRouter().open(ctx, result); } private List getNetworkIds() From 776b57e5cb707f23cf01de49c6966f0030dfdf5e Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:17:17 -0700 Subject: [PATCH 07/32] Use appropriate routers --- .../app/viewmodel/HomeViewModel.java | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java index 22df728bf0..bfc5d112eb 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java @@ -49,9 +49,11 @@ import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.repository.TokenRepository; import com.alphawallet.app.repository.entity.RealmWCSession; +import com.alphawallet.app.router.TransferRequestRouter; import com.alphawallet.app.router.ExternalBrowserRouter; import com.alphawallet.app.router.ImportTokenRouter; import com.alphawallet.app.router.MyAddressRouter; +import com.alphawallet.app.router.SendTokenRouter; import com.alphawallet.app.service.AnalyticsServiceType; import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.RealmManager; @@ -61,7 +63,6 @@ import com.alphawallet.app.ui.AddTokenActivity; import com.alphawallet.app.ui.HomeActivity; import com.alphawallet.app.ui.ImportWalletActivity; -import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.util.QRParser; import com.alphawallet.app.util.RateApp; import com.alphawallet.app.util.Utils; @@ -374,8 +375,7 @@ public void handleQRCode(Activity activity, String qrCode) case TRANSFER: props.put(QrScanResultType.KEY, QrScanResultType.ADDRESS_OR_EIP_681.getValue()); track(Analytics.Action.SCAN_QR_CODE_SUCCESS, props); - - showSend(activity, qrResult); + new TransferRequestRouter().open(activity, qrResult); break; case FUNCTION_CALL: props.put(QrScanResultType.KEY, QrScanResultType.ADDRESS_OR_EIP_681.getValue()); @@ -420,7 +420,7 @@ private void showActionSheet(Activity activity, QRResult qrResult) View.OnClickListener listener = v -> { if (v.getId() == R.id.send_to_this_address_action) { - showSend(activity, qrResult); + new SendTokenRouter().open(activity, qrResult.getAddress()); } else if (v.getId() == R.id.add_custom_token_action) { @@ -476,24 +476,6 @@ else if (v.getId() == R.id.close_action) dialog.show(); } - public void showSend(Activity ctx, QRResult result) - { - Intent intent = new Intent(ctx, SendActivity.class); - boolean sendingTokens = (result.getFunction() != null && result.getFunction().length() > 0); - String address = defaultWallet.getValue().address; - int decimals = 18; - - intent.putExtra(C.EXTRA_SENDING_TOKENS, sendingTokens); - intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, address); - intent.putExtra(C.EXTRA_NETWORKID, result.chainId); - intent.putExtra(C.EXTRA_SYMBOL, ethereumNetworkRepository.getNetworkByChain(result.chainId).symbol); - intent.putExtra(C.EXTRA_DECIMALS, decimals); - intent.putExtra(C.Key.WALLET, defaultWallet.getValue()); - intent.putExtra(C.EXTRA_AMOUNT, result); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - ctx.startActivity(intent); - } - public void showMyAddress(Activity activity) { myAddressRouter.open(activity, defaultWallet.getValue()); From a984b3c271efbf102fcb1bacfacf08c59e88d6ae Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:20:48 -0700 Subject: [PATCH 08/32] Rename Tokens class --- .../main/java/com/alphawallet/app/ui/SwapActivity.java | 8 ++++---- .../app/viewmodel/{Tokens.java => LifiTokenUtils.java} | 2 +- ...MappingTest.java => LifiTokenUtilsMappingTest.java} | 5 +++-- .../{TokensTest.java => LifiTokenUtilsTest.java} | 10 +++++----- 4 files changed, 13 insertions(+), 12 deletions(-) rename app/src/main/java/com/alphawallet/app/viewmodel/{Tokens.java => LifiTokenUtils.java} (97%) rename app/src/test/java/com/alphawallet/app/entity/{TokensMappingTest.java => LifiTokenUtilsMappingTest.java} (93%) rename app/src/test/java/com/alphawallet/app/viewmodel/{TokensTest.java => LifiTokenUtilsTest.java} (92%) diff --git a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java index a916c9c977..9ea73676b6 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java @@ -38,7 +38,7 @@ import com.alphawallet.app.util.BalanceUtils; import com.alphawallet.app.util.SwapUtils; import com.alphawallet.app.viewmodel.SwapViewModel; -import com.alphawallet.app.viewmodel.Tokens; +import com.alphawallet.app.viewmodel.LifiTokenUtils; import com.alphawallet.app.web3.entity.Web3Transaction; import com.alphawallet.app.widget.AWalletAlertDialog; import com.alphawallet.app.widget.ActionSheetDialog; @@ -420,7 +420,7 @@ private void initSourceToken(LifiToken selectedToken) private void initFromDialog(List fromTokens) { - Tokens.sortValue(fromTokens); + LifiTokenUtils.sortValue(fromTokens); sourceTokenDialog = new SelectLifiTokenDialog(fromTokens, this, tokenItem -> { sourceSelector.init(tokenItem); sourceTokenDialog.dismiss(); @@ -429,8 +429,8 @@ private void initFromDialog(List fromTokens) private void initToDialog(List toTokens) { - Tokens.sortName(toTokens); - Tokens.sortValue(toTokens); + LifiTokenUtils.sortName(toTokens); + LifiTokenUtils.sortValue(toTokens); destTokenDialog = new SelectLifiTokenDialog(toTokens, this, tokenItem -> { destSelector.init(tokenItem); destTokenDialog.dismiss(); diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java b/app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java similarity index 97% rename from app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java rename to app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java index e5c00da294..2a6592e75e 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java @@ -7,7 +7,7 @@ import java.util.Collections; import java.util.List; -public class Tokens +public class LifiTokenUtils { public static void sortValue(List tokenItems) { diff --git a/app/src/test/java/com/alphawallet/app/entity/TokensMappingTest.java b/app/src/test/java/com/alphawallet/app/entity/LifiTokenUtilsMappingTest.java similarity index 93% rename from app/src/test/java/com/alphawallet/app/entity/TokensMappingTest.java rename to app/src/test/java/com/alphawallet/app/entity/LifiTokenUtilsMappingTest.java index df579b54ce..baad86c554 100644 --- a/app/src/test/java/com/alphawallet/app/entity/TokensMappingTest.java +++ b/app/src/test/java/com/alphawallet/app/entity/LifiTokenUtilsMappingTest.java @@ -15,11 +15,12 @@ import java.util.Collection; @RunWith(Parameterized.class) -public class TokensMappingTest { +public class LifiTokenUtilsMappingTest +{ private String groupString; private TokenGroup groupEnum; - public TokensMappingTest(String groupString, TokenGroup groupEnum) { + public LifiTokenUtilsMappingTest(String groupString, TokenGroup groupEnum) { this.groupString = groupString; this.groupEnum = groupEnum; } diff --git a/app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java b/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java similarity index 92% rename from app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java rename to app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java index 9c2afa5b67..9841645d62 100644 --- a/app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java +++ b/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java @@ -10,7 +10,7 @@ import java.util.ArrayList; import java.util.List; -public class TokensTest +public class LifiTokenUtilsTest { @Test public void sort_token_by_fiat_value_in_DESC() @@ -20,7 +20,7 @@ public void sort_token_by_fiat_value_in_DESC() list.add(createToken("Binance Smart Chain", "BNB", "0x1", 1)); list.add(createToken("Solana", "SOL", "0x2", 2)); - Tokens.sortValue(list); + LifiTokenUtils.sortValue(list); assertThat(list.get(0).symbol, equalTo("SOL")); assertThat(list.get(1).symbol, equalTo("BNB")); @@ -35,7 +35,7 @@ public void sort_tokens_by_name_alphabetically() list.add(createToken("Binance Smart Chain", "BNB", "0x1", 0)); list.add(createToken("Solana", "SOL", "0x2", 0)); - Tokens.sortName(list); + LifiTokenUtils.sortName(list); assertThat(list.get(0).symbol, equalTo("BNB")); assertThat(list.get(1).symbol, equalTo("ETH")); @@ -50,7 +50,7 @@ public void sort_name_should_return_native_token_first() list.add(createToken("Stox", "STX", "0x0000000000000000000000000000000000000000", 0)); list.add(createToken("stETH", "stETH", "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 0)); - Tokens.sortName(list); + LifiTokenUtils.sortName(list); assertThat(list.get(0).symbol, equalTo("stETH")); assertThat(list.get(1).symbol, equalTo("STX")); @@ -64,7 +64,7 @@ public void sort_name_should_be_case_insensitive() list.add(createToken("Stox", "STX", "0x0", 0)); list.add(createToken("stETH", "stETH", "0x3", 0)); - Tokens.sortName(list); + LifiTokenUtils.sortName(list); assertThat(list.get(0).symbol, equalTo("stETH")); assertThat(list.get(1).symbol, equalTo("STX")); From 45b8eebc6e8b5dddcf027a251ab3034bd5215084 Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:21:33 -0700 Subject: [PATCH 09/32] Move to utils package --- app/src/main/java/com/alphawallet/app/ui/SwapActivity.java | 2 +- .../com/alphawallet/app/{viewmodel => util}/LifiTokenUtils.java | 2 +- .../java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename app/src/main/java/com/alphawallet/app/{viewmodel => util}/LifiTokenUtils.java (96%) diff --git a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java index 9ea73676b6..e07e102d7c 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java @@ -38,7 +38,7 @@ import com.alphawallet.app.util.BalanceUtils; import com.alphawallet.app.util.SwapUtils; import com.alphawallet.app.viewmodel.SwapViewModel; -import com.alphawallet.app.viewmodel.LifiTokenUtils; +import com.alphawallet.app.util.LifiTokenUtils; import com.alphawallet.app.web3.entity.Web3Transaction; import com.alphawallet.app.widget.AWalletAlertDialog; import com.alphawallet.app.widget.ActionSheetDialog; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java b/app/src/main/java/com/alphawallet/app/util/LifiTokenUtils.java similarity index 96% rename from app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java rename to app/src/main/java/com/alphawallet/app/util/LifiTokenUtils.java index 2a6592e75e..84809670bb 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java +++ b/app/src/main/java/com/alphawallet/app/util/LifiTokenUtils.java @@ -1,4 +1,4 @@ -package com.alphawallet.app.viewmodel; +package com.alphawallet.app.util; import com.alphawallet.app.entity.lifi.LifiToken; diff --git a/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java b/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java index 9841645d62..747149a7c7 100644 --- a/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java +++ b/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java @@ -4,6 +4,7 @@ import static org.hamcrest.core.IsEqual.equalTo; import com.alphawallet.app.entity.lifi.LifiToken; +import com.alphawallet.app.util.LifiTokenUtils; import org.junit.Test; From 624c91b541e7ae8a85f069dd71c7e17a3423783c Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 11 May 2023 00:04:05 -0700 Subject: [PATCH 10/32] Rename and move to utils package --- .../adapter/SelectLifiTokenAdapter.java | 7 ++++--- .../LifiTokenFilter.java} | 6 +++--- .../widget/adapter/LifiTokenFilterTest.java | 21 ++++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) rename app/src/main/java/com/alphawallet/app/{ui/widget/adapter/TokenFilter.java => util/LifiTokenFilter.java} (94%) diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java index f66db64187..84f4145dc9 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java @@ -11,6 +11,7 @@ import com.alphawallet.app.R; import com.alphawallet.app.entity.lifi.LifiToken; +import com.alphawallet.app.util.LifiTokenFilter; import com.alphawallet.app.widget.AddressIcon; import com.alphawallet.app.widget.SelectLifiTokenDialog; import com.google.android.material.radiobutton.MaterialRadioButton; @@ -22,12 +23,12 @@ public class SelectLifiTokenAdapter extends RecyclerView.Adapter displayData; private final SelectLifiTokenDialog.EventListener callback; - private final TokenFilter tokenFilter; + private final LifiTokenFilter lifiTokenFilter; private String selectedTokenAddress; public SelectLifiTokenAdapter(List tokens, SelectLifiTokenDialog.EventListener callback) { - tokenFilter = new TokenFilter(tokens); + lifiTokenFilter = new LifiTokenFilter(tokens); this.callback = callback; displayData = new ArrayList<>(); displayData.addAll(tokens); @@ -76,7 +77,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) public void filter(String keyword) { - updateList(tokenFilter.filterBy(keyword)); + updateList(lifiTokenFilter.filterBy(keyword)); } public void updateList(List filteredList) diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java b/app/src/main/java/com/alphawallet/app/util/LifiTokenFilter.java similarity index 94% rename from app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java rename to app/src/main/java/com/alphawallet/app/util/LifiTokenFilter.java index 224681ceca..34b1e59e1c 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java +++ b/app/src/main/java/com/alphawallet/app/util/LifiTokenFilter.java @@ -1,4 +1,4 @@ -package com.alphawallet.app.ui.widget.adapter; +package com.alphawallet.app.util; import android.text.TextUtils; @@ -11,11 +11,11 @@ import java.util.ListIterator; import java.util.Locale; -public class TokenFilter +public class LifiTokenFilter { private final List tokens; - public TokenFilter(List tokens) + public LifiTokenFilter(List tokens) { this.tokens = tokens; removeBadTokens(); diff --git a/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java b/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java index 99a8174d09..e43bcec822 100644 --- a/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java +++ b/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import com.alphawallet.app.entity.lifi.LifiToken; +import com.alphawallet.app.util.LifiTokenFilter; import org.junit.Before; import org.junit.Test; @@ -15,7 +16,7 @@ public class LifiTokenFilterTest { - private TokenFilter tokenFilter; + private LifiTokenFilter lifiTokenFilter; @Before public void setUp() throws Exception @@ -25,13 +26,13 @@ public void setUp() throws Exception list.add(createToken("Solana", "SOL", "2")); list.add(createToken("Binance", "BNB", "3")); list.add(createToken("", "", "4")); - tokenFilter = new TokenFilter(list); + lifiTokenFilter = new LifiTokenFilter(list); } @Test public void nameContains() { - List result = tokenFilter.filterBy("an"); + List result = lifiTokenFilter.filterBy("an"); assertThat(result.size(), equalTo(2)); assertThat(result.get(0).name, equalTo("Solana")); assertThat(result.get(1).name, equalTo("Binance")); @@ -40,7 +41,7 @@ public void nameContains() @Test public void nameStartsWith() { - List result = tokenFilter.filterBy("So"); + List result = lifiTokenFilter.filterBy("So"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); } @@ -48,7 +49,7 @@ public void nameStartsWith() @Test public void symbolContains() { - List result = tokenFilter.filterBy("B"); + List result = lifiTokenFilter.filterBy("B"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Binance")); } @@ -56,7 +57,7 @@ public void symbolContains() @Test public void symbolStartsWith() { - List result = tokenFilter.filterBy("S"); + List result = lifiTokenFilter.filterBy("S"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); } @@ -64,11 +65,11 @@ public void symbolStartsWith() @Test public void should_be_case_insensitive() { - List result = tokenFilter.filterBy("s"); + List result = lifiTokenFilter.filterBy("s"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); - result = tokenFilter.filterBy("b"); + result = lifiTokenFilter.filterBy("b"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Binance")); } @@ -80,9 +81,9 @@ public void should_sort_starts_with_in_front_of_contains() list.add(createToken("Solana", "SOL", "2")); list.add(createToken("WETH", "WETH", "2")); list.add(createToken("Ethereum", "ETH", "1")); - tokenFilter = new TokenFilter(list); + lifiTokenFilter = new LifiTokenFilter(list); - List result = tokenFilter.filterBy("eth"); + List result = lifiTokenFilter.filterBy("eth"); assertThat(result.size(), equalTo(2)); assertThat(result.get(0).name, equalTo("Ethereum")); assertThat(result.get(1).name, equalTo("WETH")); From fe0ceba21ebb09f82c4c9f047e09651306f024b6 Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 11 May 2023 00:12:58 -0700 Subject: [PATCH 11/32] Apply changes to SendActivity --- .../com/alphawallet/app/ui/SendActivity.java | 278 +++++++----------- .../ui/widget/adapter/SelectTokenAdapter.java | 12 +- .../{TokenFilter2.java => TokenFilter.java} | 12 +- .../app/viewmodel/SendViewModel.java | 113 ++++++- 4 files changed, 229 insertions(+), 186 deletions(-) rename app/src/main/java/com/alphawallet/app/util/{TokenFilter2.java => TokenFilter.java} (81%) diff --git a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java index e3c7479eb6..7596338b3f 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java @@ -1,9 +1,7 @@ package com.alphawallet.app.ui; -import static com.alphawallet.app.C.Key.WALLET; import static com.alphawallet.app.widget.AWalletAlertDialog.ERROR; import static com.alphawallet.app.widget.AWalletAlertDialog.WARNING; -import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; import android.app.Activity; import android.content.Intent; @@ -12,8 +10,6 @@ import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -25,9 +21,8 @@ import com.alphawallet.app.analytics.Analytics; import com.alphawallet.app.entity.AnalyticsProperties; import com.alphawallet.app.entity.CryptoFunctions; -import com.alphawallet.app.entity.EIP681Type; +import com.alphawallet.app.entity.ErrorEnvelope; import com.alphawallet.app.entity.GasEstimate; -import com.alphawallet.app.entity.NetworkInfo; import com.alphawallet.app.entity.Operation; import com.alphawallet.app.entity.QRResult; import com.alphawallet.app.entity.SignAuthenticationCallback; @@ -38,6 +33,7 @@ import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.repository.EthereumNetworkBase; import com.alphawallet.app.repository.EthereumNetworkRepository; +import com.alphawallet.app.router.TransferRequestRouter; import com.alphawallet.app.service.GasService; import com.alphawallet.app.ui.QRScanning.QRScannerActivity; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; @@ -54,10 +50,10 @@ import com.alphawallet.app.widget.FunctionButtonBar; import com.alphawallet.app.widget.InputAddress; import com.alphawallet.app.widget.InputAmount; +import com.alphawallet.app.widget.SelectTokenDialog; import com.alphawallet.app.widget.SignTransactionDialog; import com.alphawallet.hardware.SignatureFromKey; import com.alphawallet.token.entity.SalesOrderMalformed; -import com.alphawallet.token.tools.Convert; import com.alphawallet.token.tools.Numeric; import com.alphawallet.token.tools.ParseMagicLink; @@ -75,28 +71,31 @@ import timber.log.Timber; @AndroidEntryPoint -public class SendActivity extends BaseActivity implements AmountReadyCallback, StandardFunctionInterface, AddressReadyCallback, ActionSheetCallback +public class SendActivity extends BaseActivity implements + AmountReadyCallback, + StandardFunctionInterface, + AddressReadyCallback, + ActionSheetCallback, + SelectTokenDialog.OnTokenClickListener { private static final BigDecimal NEGATIVE = BigDecimal.ZERO.subtract(BigDecimal.ONE); - - SendViewModel viewModel; - + private final Handler handler = new Handler(); + private SendViewModel viewModel; private Wallet wallet; private Token token; - private final Handler handler = new Handler(); private AWalletAlertDialog dialog; - - private QRResult currentResult; - private InputAmount amountInput; private InputAddress addressInput; - private String sendAddress; + private FunctionButtonBar functionBar; + private String sendAddress = null; private String ensAddress; - private BigDecimal sendAmount; - private BigDecimal sendGasPrice; + private BigDecimal sendAmount = NEGATIVE; + private BigDecimal sendGasPrice = BigDecimal.ZERO; private ActionSheetDialog confirmationDialog; + private final ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + result -> confirmationDialog.setCurrentGasIndex(result)); + private SelectTokenDialog selectTokenDialog; private AWalletAlertDialog alertDialog; - @Nullable private Disposable calcGasCost; @@ -104,68 +103,76 @@ public class SendActivity extends BaseActivity implements AmountReadyCallback, S protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_send); + toolbar(); - viewModel = new ViewModelProvider(this) - .get(SendViewModel.class); + setTitle("Send"); - String contractAddress = getIntent().getStringExtra(C.EXTRA_CONTRACT_ADDRESS); - long currentChain = getIntent().getLongExtra(C.EXTRA_NETWORKID, MAINNET_ID); - wallet = getIntent().getParcelableExtra(WALLET); - token = viewModel.getToken(currentChain, getIntent().getStringExtra(C.EXTRA_ADDRESS)); - QRResult result = getIntent().getParcelableExtra(C.EXTRA_AMOUNT); + initViewModel(); - viewModel.transactionFinalised().observe(this, this::txWritten); - viewModel.transactionError().observe(this, this::txError); + initViews(); - sendAddress = null; - sendGasPrice = BigDecimal.ZERO; - sendAmount = NEGATIVE; +// evaluateQrResult(getIntent().getParcelableExtra(C.EXTRA_AMOUNT)); - if (!checkTokenValidity(currentChain, contractAddress)) - { - return; - } + viewModel.prepare(); + } - setTitle(getString(R.string.action_send_tkn, token.getShortName())); - setupTokenContent(); + private void initViews() + { + amountInput = findViewById(R.id.input_amount); + addressInput = findViewById(R.id.input_address); + functionBar = findViewById(R.id.layoutButtons); - if (result != null) - { - //restore payment request - validateEIP681Request(result, true); - } + amountInput.setListener(v -> viewModel.fetchTokens()); + addressInput.setAddressCallback(this); } - @Override - protected void onResume() + private void initViewModel() + { + viewModel = new ViewModelProvider(this) + .get(SendViewModel.class); + viewModel.wallet().observe(this, this::onWallet); + viewModel.tokens().observe(this, this::onTokens); + viewModel.transactionFinalised().observe(this, this::txWritten); + viewModel.transactionError().observe(this, this::txError); + viewModel.error().observe(this, this::onError); + } + + private void onWallet(Wallet wallet) { - super.onResume(); + this.wallet = wallet; - QRResult result = getIntent().getParcelableExtra(C.EXTRA_AMOUNT); + String recipientAddress = getIntent().getStringExtra(C.EXTRA_ADDRESS); + String tokenAddress = getIntent().getStringExtra(C.EXTRA_CONTRACT_ADDRESS); + long tokenChainId = getIntent().getLongExtra(C.EXTRA_NETWORKID, -1); - if (result != null && (result.type == EIP681Type.PAYMENT || result.type == EIP681Type.TRANSFER)) + if (!TextUtils.isEmpty(recipientAddress)) // From address qr + { + addressInput.setAddress(recipientAddress); + viewModel.fetchTokens(); + } + else if (!TextUtils.isEmpty(tokenAddress) && tokenChainId != -1) // From token detail + { + token = viewModel.getToken(tokenChainId, tokenAddress); + setupTokenContent(token); + } + else // From bottom nav { - handleClick("", R.string.action_next); + viewModel.fetchTokens(); } } - private boolean checkTokenValidity(long currentChain, String contractAddress) + private void onTokens(List tokens) { - if (token == null || token.tokenInfo == null) - { - //bad token - try to load from service - token = viewModel.getToken(currentChain, contractAddress); - - if (token == null) - { - //TODO: possibly invoke token finder in tokensService - finish(); - } - } + selectTokenDialog = new SelectTokenDialog(tokens, this, this); + selectTokenDialog.show(); + } - return (token != null); + private void onError(ErrorEnvelope errorEnvelope) + { + displayToast(errorEnvelope.message); } private void onBack() @@ -284,7 +291,7 @@ else if (requestCode == C.BARCODE_READER_REQUEST_CODE) break; default: Timber.tag("SEND").e(String.format(getString(R.string.barcode_error_format), - "Code: " + resultCode + "Code: " + resultCode )); break; } @@ -344,109 +351,26 @@ private void calculateEstimateDialog() private void validateEIP681Request(QRResult result, boolean overrideNetwork) { - if (dialog != null) dialog.dismiss(); - //check chain - if (result == null) - { - displayScanError(); - return; - } - - NetworkInfo info = viewModel.getNetworkInfo(result.chainId); - if (info == null) - { - displayScanError(); - return; - } - else if (result.type != EIP681Type.ADDRESS && result.chainId != token.tokenInfo.chainId && token.isEthereum()) - { - //Display chain change warning - currentResult = result; - showChainChangeDialog(result.chainId); - return; - } - - TextView sendText = findViewById(R.id.text_payment_request); - switch (result.type) { case ADDRESS: addressInput.setAddress(result.getAddress()); break; - case PAYMENT: - //correct chain and asset type - String ethAmount = Convert.getConvertedValue(new BigDecimal(result.weiValue), Convert.Unit.ETHER.getFactor()); - sendText.setVisibility(View.VISIBLE); - sendText.setText(R.string.transfer_request); - token = viewModel.getToken(result.chainId, wallet.address); - addressInput.setAddress(result.getAddress()); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); - amountInput.setAmount(ethAmount); - setupTokenContent(); - break; - case TRANSFER: - Token resultToken = viewModel.getToken(result.chainId, result.getAddress()); - if (resultToken == null) - { - currentResult = result; - showTokenFetch(); - viewModel.fetchToken(result.chainId, result.getAddress(), wallet.address); - } - else if (resultToken.isERC20()) - { - //ERC20 send request - token = resultToken; - setupTokenContent(); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); - //convert token amount into scaled value - String convertedAmount = Convert.getConvertedValue(result.tokenAmount, token.tokenInfo.decimals); - amountInput.setAmount(convertedAmount); - addressInput.setAddress(result.functionToAddress); - sendText.setVisibility(View.VISIBLE); - sendText.setText(getString(R.string.token_transfer_request, resultToken.getFullName())); - } - //TODO: Handle NFT eg ERC721 + new TransferRequestRouter().open(this, result); break; - case FUNCTION_CALL: //Generic function call, not handled yet displayScanError(R.string.toast_qr_code_no_address, getString(R.string.no_tokens)); if (result.functionToAddress != null) addressInput.setAddress(result.functionToAddress); break; - default: displayScanError(); } } - private void showChainChangeDialog(long chainId) - { - if (dialog != null && dialog.isShowing()) dialog.dismiss(); - dialog = new AWalletAlertDialog(this); - dialog.setIcon(AWalletAlertDialog.WARNING); - dialog.setTitle(R.string.change_chain_request); - dialog.setMessage(R.string.change_chain_message); - dialog.setButtonText(R.string.dialog_ok); - dialog.setButtonListener(v -> { - //we should change the chain. - token = viewModel.getToken(chainId, token.getAddress()); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); - dialog.dismiss(); - validateEIP681Request(currentResult, false); - }); - dialog.setSecondaryButtonText(R.string.action_cancel); - dialog.setSecondaryButtonListener(v -> { - dialog.dismiss(); - //proceed without changing the chain - currentResult.chainId = token.tokenInfo.chainId; - validateEIP681Request(currentResult, false); - }); - dialog.show(); - } - private void displayScanError() { if (dialog != null && dialog.isShowing()) dialog.dismiss(); @@ -486,20 +410,19 @@ protected void onDestroy() // addressInput.setEnsNodeNotSyncCallback(null); // prevent leak by removing reference to activity method } - private void setupTokenContent() + private void setupTokenContent(Token token) { - amountInput = findViewById(R.id.input_amount); + this.token = token; + setTitle(getString(R.string.action_send_tkn, token.getSymbol())); amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); - addressInput = findViewById(R.id.input_address); - addressInput.setAddressCallback(this); addressInput.setChainOverrideForWalletConnect(token.tokenInfo.chainId); //addressInput.setEnsHandlerNodeSyncFlag(true); // allow node sync //addressInput.setEnsNodeNotSyncCallback(this::showNodeNotSyncSheet); // callback to invoke if node not synced - FunctionButtonBar functionBar = findViewById(R.id.layoutButtons); functionBar.revealButtons(); List functions = new ArrayList<>(Collections.singletonList(R.string.action_next)); functionBar.setupFunctions(this, functions); viewModel.startGasCycle(token.tokenInfo.chainId); + amountInput.focus(); } @Override @@ -507,7 +430,7 @@ public void amountReady(BigDecimal value, BigDecimal gasPrice) { //validate that we have sufficient balance if ((token.isEthereum() && token.balance.subtract(value).compareTo(BigDecimal.ZERO) > 0) // if sending base ethereum then check we have more than just the value - || (token.getBalanceRaw().subtract(value).compareTo(BigDecimal.ZERO) >= 0)) // contract token, check sufficient token balance (gas widget will check sufficient gas) + || (token.getBalanceRaw().subtract(value).compareTo(BigDecimal.ZERO) >= 0)) // contract token, check sufficient token balance (gas widget will check sufficient gas) { sendAmount = value; sendGasPrice = gasPrice; @@ -554,7 +477,7 @@ public void addressReady(String address, String ensName) private void calculateTransactionCost() { if ((calcGasCost != null && !calcGasCost.isDisposed()) || - (confirmationDialog != null && confirmationDialog.isShowing())) return; + (confirmationDialog != null && confirmationDialog.isShowing())) return; if (sendAmount.compareTo(NEGATIVE) > 0 && Utils.isAddressValid(sendAddress)) { @@ -568,10 +491,10 @@ private void calculateTransactionCost() calculateEstimateDialog(); //form payload and calculate tx cost calcGasCost = viewModel.calculateGasEstimate(wallet, transactionBytes, token.tokenInfo.chainId, txDestAddress, BigDecimal.ZERO) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(estimate -> checkConfirm(estimate, transactionBytes, txDestAddress, txSendAddress), - error -> handleError(error, transactionBytes, token.getAddress(), txSendAddress)); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(estimate -> checkConfirm(estimate, transactionBytes, txDestAddress, txSendAddress), + error -> handleError(error, transactionBytes, token.getAddress(), txSendAddress)); } } @@ -589,15 +512,15 @@ private void checkConfirm(final GasEstimate estimate, final byte[] transactionBy BigInteger ethValue = token.isEthereum() ? sendAmount.toBigInteger() : BigInteger.ZERO; long leafCode = amountInput.isSendAll() ? -2 : -1; Web3Transaction w3tx = new Web3Transaction( - new Address(txSendAddress), - token.isEthereum() ? null : new Address(token.getAddress()), - new Address(wallet.address), - ethValue, - sendGasPrice.toBigInteger(), - estimate.getValue(), - -1, - Numeric.toHexString(transactionBytes), - leafCode); + new Address(txSendAddress), + token.isEthereum() ? null : new Address(token.getAddress()), + new Address(wallet.address), + ethValue, + sendGasPrice.toBigInteger(), + estimate.getValue(), + -1, + Numeric.toHexString(transactionBytes), + leafCode); if (estimate.hasError() || estimate.getValue().equals(BigInteger.ZERO)) { @@ -607,7 +530,7 @@ private void checkConfirm(final GasEstimate estimate, final byte[] transactionBy { if (dialog != null && dialog.isShowing()) dialog.dismiss(); confirmationDialog = new ActionSheetDialog(this, w3tx, token, ensAddress, - resolvedAddress, viewModel.getTokenService(), this); + resolvedAddress, viewModel.getTokenService(), this); confirmationDialog.setCanceledOnTouchOutside(false); confirmationDialog.show(); sendAmount = NEGATIVE; @@ -652,9 +575,6 @@ public void dismissed(String txHash, long callbackId, boolean actionCompleted) } } - ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> confirmationDialog.setCurrentGasIndex(result)); - @Override public ActivityResultLauncher gasSelectLauncher() { @@ -678,6 +598,11 @@ public WalletType getWalletType() private void txWritten(TransactionReturn txData) { confirmationDialog.transactionWritten(txData.hash); + viewModel.setLastSentToken(token); +// viewModel.setLastSentTokenAddress(txData.tx.contract != null ? +// txData.tx.contract.toString() : +// txData.tx.sender.toString() +// ); } //Transaction failed to be sent @@ -703,7 +628,7 @@ private void estimateError(GasEstimate estimate, final Web3Transaction w3tx, fin dialog.setTitle(estimate.hasError() ? R.string.dialog_title_gas_estimation_failed : R.string.confirm_transaction - ); + ); String message = estimate.hasError() ? getString(R.string.dialog_message_gas_estimation_failed, estimate.getError()) : getString(R.string.error_transaction_may_fail); @@ -773,4 +698,15 @@ void showTxnTimeoutDialog() }); dialog.show(); } + + @Override + public void onTokenClicked(Token token) + { + if (selectTokenDialog != null && selectTokenDialog.isShowing()) + { + selectTokenDialog.dismiss(); + } + setupTokenContent(token); + Timber.d("juz here onTokenClicked"); + } } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java index 97ea39213a..03ebdb99cc 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java @@ -1,5 +1,6 @@ package com.alphawallet.app.ui.widget.adapter; +import android.annotation.SuppressLint; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -11,7 +12,7 @@ import com.alphawallet.app.R; import com.alphawallet.app.entity.tokens.Token; -import com.alphawallet.app.util.TokenFilter2; +import com.alphawallet.app.util.TokenFilter; import com.alphawallet.app.widget.SelectTokenDialog; import com.alphawallet.app.widget.TokenIcon; @@ -21,13 +22,13 @@ public class SelectTokenAdapter extends RecyclerView.Adapter { private final List displayData; - private final TokenFilter2 tokenFilter; + private final TokenFilter tokenFilter; private final SelectTokenDialog.OnTokenClickListener listener; public SelectTokenAdapter(List tokens, SelectTokenDialog.OnTokenClickListener listener) { this.listener = listener; - tokenFilter = new TokenFilter2(tokens); + tokenFilter = new TokenFilter(tokens); displayData = new ArrayList<>(); displayData.addAll(tokens); } @@ -73,11 +74,12 @@ public void filter(String keyword) updateList(tokenFilter.filterBy(keyword)); } + @SuppressLint("NotifyDataSetChanged") public void updateList(List filteredList) { displayData.clear(); displayData.addAll(filteredList); - notifyItemRangeChanged(0, filteredList.size()); + notifyDataSetChanged(); } @Override @@ -102,6 +104,4 @@ static class ViewHolder extends RecyclerView.ViewHolder tokenIcon = view.findViewById(R.id.token_icon); } } - - } diff --git a/app/src/main/java/com/alphawallet/app/util/TokenFilter2.java b/app/src/main/java/com/alphawallet/app/util/TokenFilter.java similarity index 81% rename from app/src/main/java/com/alphawallet/app/util/TokenFilter2.java rename to app/src/main/java/com/alphawallet/app/util/TokenFilter.java index afd6f3ceaa..4664f4f4b4 100644 --- a/app/src/main/java/com/alphawallet/app/util/TokenFilter2.java +++ b/app/src/main/java/com/alphawallet/app/util/TokenFilter.java @@ -1,5 +1,7 @@ package com.alphawallet.app.util; +import android.text.TextUtils; + import androidx.annotation.NonNull; import com.alphawallet.app.entity.tokens.Token; @@ -8,17 +10,23 @@ import java.util.List; import java.util.Locale; -public class TokenFilter2 +public class TokenFilter { private final List tokens; - public TokenFilter2(List tokens) + private final List copy; + + public TokenFilter(List tokens) { this.tokens = tokens; + this.copy = new ArrayList<>(); + this.copy.addAll(tokens); } public List filterBy(String keyword) { + if (TextUtils.isEmpty(keyword)) return copy; + String lowerCaseKeyword = lowerCase(keyword); List result = new ArrayList<>(); diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/SendViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/SendViewModel.java index 9c91b3b9ca..89c74c1167 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/SendViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/SendViewModel.java @@ -3,7 +3,9 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.util.Pair; +import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import com.alphawallet.app.C; @@ -14,9 +16,13 @@ import com.alphawallet.app.entity.TransactionReturn; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.tokens.Token; +import com.alphawallet.app.entity.tokens.TokenCardMeta; import com.alphawallet.app.entity.tokens.TokenInfo; import com.alphawallet.app.interact.CreateTransactionInteract; +import com.alphawallet.app.interact.FetchTokensInteract; +import com.alphawallet.app.interact.GenericWalletInteract; import com.alphawallet.app.repository.EthereumNetworkRepositoryType; +import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.repository.TokenRepository; import com.alphawallet.app.router.MyAddressRouter; import com.alphawallet.app.service.AnalyticsServiceType; @@ -31,6 +37,10 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; import javax.inject.Inject; @@ -45,6 +55,8 @@ public class SendViewModel extends BaseViewModel implements TransactionSendHandl private final MutableLiveData finalisedToken = new MutableLiveData<>(); private final MutableLiveData transactionFinalised = new MutableLiveData<>(); private final MutableLiveData transactionError = new MutableLiveData<>(); + private final MutableLiveData wallet = new MutableLiveData<>(); + private final MutableLiveData> tokens = new MutableLiveData<>(); private final MyAddressRouter myAddressRouter; private final EthereumNetworkRepositoryType networkRepository; @@ -53,6 +65,9 @@ public class SendViewModel extends BaseViewModel implements TransactionSendHandl private final AssetDefinitionService assetDefinitionService; private final KeyService keyService; private final CreateTransactionInteract createTransactionInteract; + private final PreferenceRepositoryType preferenceRepository; + private final GenericWalletInteract genericWalletInteract; + private final FetchTokensInteract fetchTokensInteract; @Inject public SendViewModel(MyAddressRouter myAddressRouter, @@ -62,7 +77,10 @@ public SendViewModel(MyAddressRouter myAddressRouter, GasService gasService, AssetDefinitionService assetDefinitionService, KeyService keyService, - AnalyticsServiceType analyticsService) + AnalyticsServiceType analyticsService, + PreferenceRepositoryType preferenceRepository, + GenericWalletInteract genericWalletInteract, + FetchTokensInteract fetchTokensInteract) { this.myAddressRouter = myAddressRouter; this.networkRepository = ethereumNetworkRepositoryType; @@ -71,9 +89,85 @@ public SendViewModel(MyAddressRouter myAddressRouter, this.assetDefinitionService = assetDefinitionService; this.keyService = keyService; this.createTransactionInteract = createTransactionInteract; + this.preferenceRepository = preferenceRepository; + this.genericWalletInteract = genericWalletInteract; + this.fetchTokensInteract = fetchTokensInteract; setAnalyticsService(analyticsService); } + public void prepare() + { + disposable = genericWalletInteract.find() + .observeOn(Schedulers.io()) + .subscribeOn(Schedulers.io()) + .subscribe(wallet::postValue, this::onError); + } + + public void fetchTokens() + { + disposable = fetchTokensInteract.fetchTokenMetas(wallet.getValue(), tokensService.getNetworkFilters(), assetDefinitionService) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::onTokenMetas, this::onError); + } + + private void onTokenMetas(TokenCardMeta[] tokenCardMetas) + { + Arrays.sort(tokenCardMetas, Comparator.comparing(TokenCardMeta::getNameWeight)); + List tokenList = new ArrayList<>(); + int i = 0; + for (TokenCardMeta meta : tokenCardMetas) + { + Pair lastSentToken = getLastSentToken(); + Token t = tokensService.getToken(meta.getChain(), meta.getAddress()); + if (t.balance.compareTo(BigDecimal.ZERO) > 0 && !t.isNonFungible()) + { + boolean isLastSentToken = t.tokenInfo.address.equalsIgnoreCase(lastSentToken.first); + if (i == 0 && isLastSentToken && t.tokenInfo.chainId == lastSentToken.second) + { + tokenList.add(i++, t); // Insert at the top + } + else if (i > 0 && isLastSentToken) + { + // Will only trigger if last sent token is a network token + // List other network tokens below last sent + tokenList.add(i++, t); + } + else + { + tokenList.add(t); + } + } + } + + tokens.postValue(tokenList); + } + + public Pair getLastSentToken() + { + return preferenceRepository.getLastSentToken(); + } + + public void setLastSentToken(Token token) + { + preferenceRepository.setLastSentToken(token); + } + + public LiveData> tokens() + { + return tokens; + } + + public MutableLiveData wallet() + { + return wallet; + } + + public MutableLiveData finalisedToken() + { + return finalisedToken; + } + public MutableLiveData transactionFinalised() { return transactionFinalised; @@ -94,6 +188,11 @@ public NetworkInfo getNetworkInfo(long chainId) return networkRepository.getNetworkByChain(chainId); } + public boolean isNetworkEnabled(long chainId) + { + return networkRepository.getFilterNetworkList().contains(chainId); + } + public Token getToken(long chainId, String tokenAddress) { return tokensService.getToken(chainId, tokenAddress); @@ -110,17 +209,17 @@ public void showImportLink(Context context, String importTxt) public void fetchToken(long chainId, String address, String walletAddress) { tokensService.update(address, chainId, ContractType.NOT_SET) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(tokenInfo -> gotTokenUpdate(tokenInfo, walletAddress), this::onError).isDisposed(); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(tokenInfo -> gotTokenUpdate(tokenInfo, walletAddress), this::onError).isDisposed(); } private void gotTokenUpdate(TokenInfo tokenInfo, String walletAddress) { disposable = tokensService.addToken(tokenInfo, walletAddress) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(finalisedToken::postValue, this::onError); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(finalisedToken::postValue, this::onError); } public AssetDefinitionService getAssetDefinitionService() From abb85ad8eede5ed5b3c9e91b2ff5e52927c29b13 Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 11 May 2023 00:16:34 -0700 Subject: [PATCH 12/32] Add new methods to InputAddress --- .../com/alphawallet/app/widget/InputAddress.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/alphawallet/app/widget/InputAddress.java b/app/src/main/java/com/alphawallet/app/widget/InputAddress.java index f21de52ad8..898742e876 100644 --- a/app/src/main/java/com/alphawallet/app/widget/InputAddress.java +++ b/app/src/main/java/com/alphawallet/app/widget/InputAddress.java @@ -328,7 +328,11 @@ public void ENSResolved(String address, String ens) { errorText.setVisibility(View.GONE); setWaitingSpinner(false); - bindAvatar(address, ens); + if (!TextUtils.isEmpty(address) && !TextUtils.isEmpty(ens)) + { + bindAvatar(address, ens); + } + if (addressReadyCallback != null) { addressReadyCallback.resolvedAddress(address, ens); @@ -552,6 +556,16 @@ public long getChain() return chainOverride; } + public void showControls(boolean show) + { + pasteItem.setVisibility(show ? View.VISIBLE : View.GONE); + } + + public void setEditable(boolean editable) + { + editText.setEnabled(editable); + } + /*public void setEnsNodeNotSyncCallback(EnsNodeNotSyncCallback callback) { Timber.d("setEnsNodeNotSyncCallback: "); From e295a915e4caf83a33e50cacc2e4a40b5fa7d632 Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 11 May 2023 23:26:09 -0700 Subject: [PATCH 13/32] UI fixes/updates --- app/src/main/AndroidManifest.xml | 2 +- .../com/alphawallet/app/ui/SendActivity.java | 17 +- .../app/ui/TransferRequestActivity.java | 22 +- .../com/alphawallet/app/widget/ChainName.java | 18 +- .../alphawallet/app/widget/InputAmount.java | 264 ++++++++++++------ .../app/widget/StandardHeader.java | 12 +- .../res/drawable/background_chain_inverse.xml | 2 +- .../res/drawable/background_chain_name.xml | 2 +- app/src/main/res/layout/activity_send.xml | 12 - .../res/layout/activity_transfer_request.xml | 12 - .../main/res/layout/dialog_sign_method.xml | 1 - app/src/main/res/layout/item_chain_name.xml | 19 +- app/src/main/res/layout/item_input_amount.xml | 123 +++++--- app/src/main/res/layout/item_switch_chain.xml | 11 +- 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/dimens.xml | 1 + app/src/main/res/values/strings.xml | 3 +- 22 files changed, 331 insertions(+), 196 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index be69dfb1dc..e45c3b2f66 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -364,7 +364,7 @@ + android:label="@string/transfer_request" /> viewModel.fetchTokens()); addressInput.setAddressCallback(this); + amountInput.setListener(v -> showTokenSelectDialog()); } private void initViewModel() @@ -151,7 +151,7 @@ private void onWallet(Wallet wallet) if (!TextUtils.isEmpty(recipientAddress)) // From address qr { addressInput.setAddress(recipientAddress); - viewModel.fetchTokens(); + showTokenSelectDialog(); } else if (!TextUtils.isEmpty(tokenAddress) && tokenChainId != -1) // From token detail { @@ -160,14 +160,17 @@ else if (!TextUtils.isEmpty(tokenAddress) && tokenChainId != -1) // From token d } else // From bottom nav { - viewModel.fetchTokens(); + showTokenSelectDialog(); } } private void onTokens(List tokens) { selectTokenDialog = new SelectTokenDialog(tokens, this, this); - selectTokenDialog.show(); + if (!selectTokenDialog.isShowing()) + { + selectTokenDialog.show(); + } } private void onError(ErrorEnvelope errorEnvelope) @@ -699,6 +702,11 @@ void showTxnTimeoutDialog() dialog.show(); } + private void showTokenSelectDialog() + { + viewModel.fetchTokens(); + } + @Override public void onTokenClicked(Token token) { @@ -707,6 +715,5 @@ public void onTokenClicked(Token token) selectTokenDialog.dismiss(); } setupTokenContent(token); - Timber.d("juz here onTokenClicked"); } } diff --git a/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java b/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java index 6b053993ff..860f22f9c6 100644 --- a/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java @@ -112,15 +112,11 @@ private void initViews() amountInput = findViewById(R.id.input_amount); addressInput = findViewById(R.id.input_address); functionBar = findViewById(R.id.layoutButtons); - addressInput.setAddressCallback(this); - amountInput.hideCaret(); - - //TODO Send: set uneditable amount and address input. - amountInput.setEnabled(false); -// addressInput.setEnabled(false); addressInput.setEditable(false); addressInput.showControls(false); + amountInput.setEditable(false); + amountInput.showControls(false); progressDialog = new AWalletAlertDialog(this); progressDialog.setTitle(R.string.searching_for_token); @@ -164,10 +160,12 @@ private void onWallet(Wallet wallet) } else if (result.type == EIP681Type.PAYMENT) { + setTitle(getString(R.string.title_payment_request)); token = viewModel.getToken(result.chainId, wallet.address); } else if (result.type == EIP681Type.TRANSFER) { + setTitle(getString(R.string.transfer_request)); token = viewModel.getToken(result.chainId, result.getAddress()); } @@ -177,7 +175,7 @@ else if (result.type == EIP681Type.TRANSFER) } else { - Timber.d("You don't have this token, attempt to fetch"); + // You don't have this token, attempt to fetch showProgress(true); viewModel.fetchToken(result.chainId, result.getAddress(), wallet.address); } @@ -232,21 +230,11 @@ private void onFinalisedToken(Token token) evaluateToken(token); } -// private void onTokens(List tokens) -// { -// // Filter tokens here. -// selectTokenDialog = new SelectTokenDialog(tokens, this, this); -// selectTokenDialog.show(); -// } - private void setupTokenContent(Token token) { this.token = token; amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); addressInput.setChainOverrideForWalletConnect(token.tokenInfo.chainId); - - setTitle(getString(R.string.action_send_tkn, token.getSymbol())); - functionBar.revealButtons(); functionBar.setupFunctions(this, new ArrayList<>(Collections.singletonList(R.string.action_next))); viewModel.startGasCycle(token.tokenInfo.chainId); diff --git a/app/src/main/java/com/alphawallet/app/widget/ChainName.java b/app/src/main/java/com/alphawallet/app/widget/ChainName.java index c7038ca512..83ff8453f1 100644 --- a/app/src/main/java/com/alphawallet/app/widget/ChainName.java +++ b/app/src/main/java/com/alphawallet/app/widget/ChainName.java @@ -2,9 +2,7 @@ import android.content.Context; import android.content.res.TypedArray; -import android.text.TextUtils; import android.util.AttributeSet; -import android.util.TypedValue; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; @@ -20,6 +18,7 @@ */ public class ChainName extends LinearLayout { + private final LinearLayout layout; private final TextView chainName; private boolean invertNameColour; @@ -27,6 +26,7 @@ public ChainName(Context context, @Nullable AttributeSet attrs) { super(context, attrs); inflate(context, R.layout.item_chain_name, this); + layout = findViewById(R.id.layout_chain_name); chainName = findViewById(R.id._text_chain_name); getAttrs(context, attrs); } @@ -41,12 +41,12 @@ public void setChainID(long chainId) if (invertNameColour) { chainName.setTextColor(getContext().getColor(EthereumNetworkBase.getChainColour(chainId))); - chainName.setBackgroundResource(R.drawable.background_chain_inverse); + layout.setBackgroundResource(R.drawable.background_chain_inverse); } else { chainName.setTextColor(getContext().getColor(R.color.white)); - chainName.getBackground().setTint(ContextCompat.getColor(getContext(), + layout.getBackground().setTint(ContextCompat.getColor(getContext(), EthereumNetworkBase.getChainColour(chainId))); } } @@ -59,15 +59,15 @@ public void setChainID(long chainId) private void getAttrs(Context context, AttributeSet attrs) { TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.InputView, - 0, 0 + attrs, + R.styleable.InputView, + 0, 0 ); try { - int fontSize = a.getInteger(R.styleable.InputView_font_size, 12); - chainName.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); +// int fontSize = a.getInteger(R.styleable.InputView_font_size, 12); +// chainName.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); invertNameColour = a.getBoolean(R.styleable.InputView_invert, false); } finally diff --git a/app/src/main/java/com/alphawallet/app/widget/InputAmount.java b/app/src/main/java/com/alphawallet/app/widget/InputAmount.java index 4d49ba8deb..6b214c2221 100644 --- a/app/src/main/java/com/alphawallet/app/widget/InputAmount.java +++ b/app/src/main/java/com/alphawallet/app/widget/InputAmount.java @@ -1,15 +1,21 @@ package com.alphawallet.app.widget; +import static com.alphawallet.app.C.GAS_LIMIT_MIN; +import static com.alphawallet.app.repository.TokensRealmSource.databaseKey; + import android.content.Context; import android.content.res.TypedArray; import android.os.Handler; import android.os.Looper; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.View; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; +import android.widget.RelativeLayout; import android.widget.TextView; import com.alphawallet.app.R; @@ -26,6 +32,7 @@ import com.alphawallet.app.ui.widget.entity.AmountReadyCallback; import com.alphawallet.app.ui.widget.entity.NumericInput; import com.alphawallet.app.util.BalanceUtils; +import com.google.android.material.button.MaterialButton; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,9 +48,6 @@ import io.realm.RealmQuery; import timber.log.Timber; -import static com.alphawallet.app.C.GAS_LIMIT_MIN; -import static com.alphawallet.app.repository.TokensRealmSource.databaseKey; - /** * Created by JB on 10/11/2020. */ @@ -54,9 +58,15 @@ public class InputAmount extends LinearLayout private final TextView symbolText; private final TokenIcon icon; private final StandardHeader header; - private final TextView availableSymbol; + private final RelativeLayout headerLayout; + private final ChainName chainName; private final TextView availableAmount; + private final TextView equivalent; private final TextView allFunds; + private final ImageView switchButton; + private final ImageView caret; + private final LinearLayout clickMore; + private final MaterialButton selectTokenButton; private final ProgressBar gasFetch; private Token token; private Realm realm; @@ -68,12 +78,12 @@ public class InputAmount extends LinearLayout private final Handler handler = new Handler(Looper.getMainLooper()); private AmountReadyCallback amountReadyCallback; private boolean amountReady; - + private boolean showingCrypto; + private boolean isEditable = true; //These need to be members because the listener is shut down if the object doesn't exist private RealmTokenTicker realmTickerUpdate; private RealmToken realmTokenUpdate; - private boolean showingCrypto; public InputAmount(Context context, AttributeSet attrs) { @@ -85,16 +95,50 @@ public InputAmount(Context context, AttributeSet attrs) symbolText = findViewById(R.id.text_token_symbol); icon = findViewById(R.id.token_icon); header = findViewById(R.id.header); - availableSymbol = findViewById(R.id.text_symbol); + headerLayout = findViewById(R.id.layout_header); + chainName = findViewById(R.id.chain); availableAmount = findViewById(R.id.text_available); allFunds = findViewById(R.id.text_all_funds); gasFetch = findViewById(R.id.gas_fetch_progress); + clickMore = findViewById(R.id.layout_more_click); + selectTokenButton = findViewById(R.id.btn_select_token); + switchButton = findViewById(R.id.btn_switch); + caret = findViewById(R.id.expand_more); + equivalent = findViewById(R.id.equivalent); showingCrypto = !CustomViewSettings.inputAmountFiatDefault(); amountReady = false; setupAttrs(context, attrs); + } - setupViewListeners(); + private void setupAttrs(Context context, AttributeSet attrs) + { + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.InputView, + 0, 0 + ); + + try + { + boolean showHeader = a.getBoolean(R.styleable.InputView_show_header, true); + boolean showAllFunds = a.getBoolean(R.styleable.InputView_show_allFunds, true); + boolean showChainName = a.getBoolean(R.styleable.InputView_showChainName, true); + boolean currencyMode = a.getBoolean(R.styleable.InputView_currencyMode, false); + int headerTextId = a.getResourceId(R.styleable.InputView_label, R.string.amount); + headerLayout.setVisibility(showHeader ? View.VISIBLE : View.GONE); + allFunds.setVisibility(showAllFunds ? View.VISIBLE : View.GONE); + header.setText(headerTextId); + if (currencyMode) + { + symbolText.setText(TickerService.getCurrencySymbolTxt()); + icon.showLocalCurrency(); + } + } + finally + { + a.recycle(); + } } /** @@ -105,21 +149,34 @@ public InputAmount(Context context, AttributeSet attrs) * @param assetDefinitionService * @param svs */ - public void setupToken(@NotNull Token token, @Nullable AssetDefinitionService assetDefinitionService, - @NotNull TokensService svs, @NotNull AmountReadyCallback amountCallback) + public void setupToken(@NotNull Token token, + @Nullable AssetDefinitionService assetDefinitionService, + @NotNull TokensService svs, + @NotNull AmountReadyCallback amountCallback) { this.token = token; this.tokensService = svs; this.assetService = assetDefinitionService; this.amountReadyCallback = amountCallback; - icon.bindData(token, assetService); - header.getChainName().setChainID(token.tokenInfo.chainId); - updateAvailableBalance(); - this.realm = tokensService.getWalletRealmInstance(); this.tickerRealm = tokensService.getTickerRealmInstance(); + + selectTokenButton.setVisibility(View.GONE); + clickMore.setVisibility(View.VISIBLE); + chainName.setVisibility(View.VISIBLE); + chainName.setChainID(token.tokenInfo.chainId); + + icon.bindData(token, assetService); + bindDataSource(); + setupAllFunds(); + + setupViewListeners(); + + updateAvailableBalance(); + + updateEquivalent(); } public void getInputAmount() @@ -215,14 +272,14 @@ private void bindDataSource() if (realmTokenUpdate != null) realmTokenUpdate.removeAllChangeListeners(); realmTokenUpdate = realm.where(RealmToken.class) - .equalTo("address", databaseKey(token.tokenInfo.chainId, token.tokenInfo.address.toLowerCase()), Case.INSENSITIVE) - .findFirstAsync(); + .equalTo("address", databaseKey(token.tokenInfo.chainId, token.tokenInfo.address.toLowerCase()), Case.INSENSITIVE) + .findFirstAsync(); //if the token doesn't exist yet, first ask the TokensService to pick it up tokensService.storeToken(token); realmTokenUpdate.addChangeListener(realmToken -> { - RealmToken rt = (RealmToken)realmToken; + RealmToken rt = (RealmToken) realmToken; if (rt.isValid() && exactAmount.compareTo(BigDecimal.ZERO) == 0) { token = tokensService.getToken(rt.getChainId(), rt.getTokenAddress()); @@ -231,37 +288,59 @@ private void bindDataSource() }); } - private void setupViewListeners() + public void setListener(OnClickListener listener) { - LinearLayout clickMore = findViewById(R.id.layout_more_click); - - clickMore.setOnClickListener(v -> { - //on down caret clicked - switch to fiat currency equivalent if there's a ticker - if (getTickerQuery() == null) return; + if (listener != null) + { + caret.setVisibility(View.VISIBLE); + clickMore.setOnClickListener(listener); + selectTokenButton.setOnClickListener(listener); + } + else + { + caret.setVisibility(View.GONE); + } + } - RealmTokenTicker rtt = getTickerQuery().findFirst(); - if (showingCrypto && rtt != null) - { - showingCrypto = false; - startTickerListener(); - } - else - { - showingCrypto = true; - if (tickerRealm != null) tickerRealm.removeAllChangeListeners(); //stop ticker listener - } + private void setupViewListeners() + { + if (getTickerQuery() != null && isEditable) + { + switchButton.setVisibility(View.VISIBLE); + switchButton.setOnClickListener(v -> { + RealmTokenTicker rtt = getTickerQuery().findFirst(); + if (showingCrypto && rtt != null) + { + showingCrypto = false; + startTickerListener(); + } + else + { + showingCrypto = true; + if (tickerRealm != null) + tickerRealm.removeAllChangeListeners(); //stop ticker listener + } - updateAvailableBalance(); - }); + updateAvailableBalance(); + updateEquivalent(); + }); + } + else + { + switchButton.setVisibility(View.GONE); + } - editText.addTextChangedListener(new TextWatcher() { + editText.addTextChangedListener(new TextWatcher() + { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + public void beforeTextChanged(CharSequence s, int start, int count, int after) + { } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + public void onTextChanged(CharSequence s, int start, int before, int count) + { if (editText.hasFocus()) { exactAmount = BigDecimal.ZERO; //invalidate the 'all funds' amount @@ -270,7 +349,9 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override - public void afterTextChanged(Editable s) { + public void afterTextChanged(Editable s) + { + updateEquivalent(); if (editText.hasFocus()) { amountReadyCallback.updateCryptoAmount(getWeiInputAmount()); @@ -295,7 +376,7 @@ private RealmQuery getTickerQuery() if (tickerRealm != null) { return tickerRealm.where(RealmTokenTicker.class) - .equalTo("contract", TokensRealmSource.databaseKey(token.tokenInfo.chainId, token.isEthereum() ? "eth" : token.getAddress().toLowerCase())); + .equalTo("contract", TokensRealmSource.databaseKey(token.tokenInfo.chainId, token.isEthereum() ? "eth" : token.getAddress().toLowerCase())); } else { @@ -317,11 +398,40 @@ private void showCrypto() { icon.bindData(token, assetService); symbolText.setText(token.getSymbol()); - availableSymbol.setText(token.getSymbol()); - availableAmount.setText(token.getStringBalanceForUI(5)); + + availableAmount.setText(String.format("%s: %s %s", context.getString(R.string.balance), token.getStringBalanceForUI(5), token.getSymbol())); + updateAllFundsAmount(); } + private void updateEquivalent() + { + if (getTickerQuery() == null || TextUtils.isEmpty(getTickerQuery().findFirst().getPrice())) + { + equivalent.setVisibility(View.GONE); + switchButton.setVisibility(View.GONE); + return; + } + + double cryptoRate = Double.parseDouble(getTickerQuery().findFirst().getPrice()); + BigDecimal amount = editText.getBigDecimalValue(); + BigDecimal rate = new BigDecimal(cryptoRate); + + BigDecimal value; + if (showingCrypto) + { + value = amount.multiply(rate).setScale(2, RoundingMode.CEILING); + equivalent.setText(String.format("%s %s", getTickerQuery().findFirst().getCurrencySymbol(), value)); + } + else + { + value = amount.divide(rate, 4, RoundingMode.FLOOR); + equivalent.setText(String.format("%s %s", value, token.tokenInfo.symbol)); + } + + equivalent.setVisibility(View.VISIBLE); + } + private void showFiat() { icon.showLocalCurrency(); @@ -335,18 +445,18 @@ private void showFiat() if (rtt != null) { - String currencyLabel = rtt.getCurrencySymbol() + TickerService.getCurrencySymbol(); + String currencyLabel = rtt.getCurrencySymbol(); symbolText.setText(currencyLabel); //calculate available fiat double cryptoRate = Double.parseDouble(rtt.getPrice()); double availableCryptoBalance = token.getCorrectedBalance(18).doubleValue(); - availableAmount.setText(TickerService.getCurrencyString(availableCryptoBalance * cryptoRate)); - availableSymbol.setText(rtt.getCurrencySymbol()); + BigDecimal balance = new BigDecimal(cryptoRate).multiply(new BigDecimal(availableCryptoBalance)).setScale(2, RoundingMode.FLOOR); + + availableAmount.setText(String.format("%s: %s %s", context.getString(R.string.balance), balance.toString(), rtt.getCurrencySymbol())); + updateAllFundsAmount(); //update amount if showing 'All Funds' - amountReadyCallback.updateCryptoAmount( - getWeiInputAmount() - ); //now update + amountReadyCallback.updateCryptoAmount(getWeiInputAmount()); //now update } updateAllFundsAmount(); } @@ -384,8 +494,8 @@ private void setupAllFunds() if (token.isEthereum() && token.hasPositiveBalance()) { RealmGasSpread gasSpread = tokensService.getTickerRealmInstance().where(RealmGasSpread.class) - .equalTo("chainId", token.tokenInfo.chainId) - .findFirst(); + .equalTo("chainId", token.tokenInfo.chainId) + .findFirst(); if (gasSpread != null && gasSpread.getGasPrice().compareTo(BigInteger.ZERO) > 0) { @@ -397,8 +507,8 @@ private void setupAllFunds() gasFetch.setVisibility(View.VISIBLE); Web3j web3j = TokenRepository.getWeb3jService(token.tokenInfo.chainId); web3j.ethGasPrice().sendAsync() - .thenAccept(ethGasPrice -> onLatestGasPrice(ethGasPrice.getGasPrice())) - .exceptionally(this::onGasFetchError); + .thenAccept(ethGasPrice -> onLatestGasPrice(ethGasPrice.getGasPrice())) + .exceptionally(this::onGasFetchError); } } else @@ -446,37 +556,6 @@ public void run() } }; - private void setupAttrs(Context context, AttributeSet attrs) - { - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.InputView, - 0, 0 - ); - - try - { - boolean showHeader = a.getBoolean(R.styleable.InputView_show_header, true); - boolean showAllFunds = a.getBoolean(R.styleable.InputView_show_allFunds, true); - boolean showChainName = a.getBoolean(R.styleable.InputView_showChainName, true); - boolean currencyMode = a.getBoolean(R.styleable.InputView_currencyMode, false); - int headerTextId = a.getResourceId(R.styleable.InputView_label, R.string.amount); - header.setVisibility(showHeader ? View.VISIBLE : View.GONE); - allFunds.setVisibility(showAllFunds ? View.VISIBLE : View.GONE); - header.setText(headerTextId); - header.getChainName().setVisibility(showChainName ? View.VISIBLE : View.GONE); - if (currencyMode) - { - symbolText.setText(TickerService.getCurrencySymbolTxt()); - icon.showLocalCurrency(); - } - } - finally - { - a.recycle(); - } - } - private Void onGasFetchError(Throwable throwable) { gasFetch.setVisibility(View.GONE); @@ -553,4 +632,21 @@ public boolean isSendAll() { return exactAmount.compareTo(BigDecimal.ZERO) > 0; } + + public void focus() + { + editText.requestFocus(); + } + + public void showControls(boolean show) + { + switchButton.setVisibility(show ? View.VISIBLE : View.GONE); + allFunds.setVisibility(show ? View.VISIBLE : View.GONE); + } + + public void setEditable(boolean editable) + { + this.isEditable = editable; + editText.setEnabled(editable); + } } diff --git a/app/src/main/java/com/alphawallet/app/widget/StandardHeader.java b/app/src/main/java/com/alphawallet/app/widget/StandardHeader.java index 2cd0a27315..164de1a6a4 100644 --- a/app/src/main/java/com/alphawallet/app/widget/StandardHeader.java +++ b/app/src/main/java/com/alphawallet/app/widget/StandardHeader.java @@ -63,22 +63,30 @@ private void getAttrs(Context context, AttributeSet attrs) if (showTextControl) { textControl.setVisibility(View.VISIBLE); - textControl.setText(controlText); } else { textControl.setVisibility(View.GONE); } + if (controlText != -1) + { + textControl.setText(controlText); + } + if (showImageControl) { imageControl.setVisibility(View.VISIBLE); - imageControl.setImageResource(controlImageRes); } else { imageControl.setVisibility(View.GONE); } + + if (controlImageRes != -1) + { + imageControl.setImageResource(controlImageRes); + } } finally { diff --git a/app/src/main/res/drawable/background_chain_inverse.xml b/app/src/main/res/drawable/background_chain_inverse.xml index 74e5f5728c..8c0f89c2d2 100644 --- a/app/src/main/res/drawable/background_chain_inverse.xml +++ b/app/src/main/res/drawable/background_chain_inverse.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_chain_name.xml b/app/src/main/res/drawable/background_chain_name.xml index 926e786462..f78e8d3ac0 100644 --- a/app/src/main/res/drawable/background_chain_name.xml +++ b/app/src/main/res/drawable/background_chain_name.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_send.xml b/app/src/main/res/layout/activity_send.xml index 594d82b07f..66cbd703fb 100644 --- a/app/src/main/res/layout/activity_send.xml +++ b/app/src/main/res/layout/activity_send.xml @@ -26,18 +26,6 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - - diff --git a/app/src/main/res/layout/item_chain_name.xml b/app/src/main/res/layout/item_chain_name.xml index ad0545b789..7d0ccf145a 100644 --- a/app/src/main/res/layout/item_chain_name.xml +++ b/app/src/main/res/layout/item_chain_name.xml @@ -1,21 +1,24 @@ + android:id="@+id/layout_chain_name" + android:background="@drawable/background_chain_name" + android:orientation="horizontal" + android:gravity="center" + android:paddingStart="@dimen/standard_16" + android:paddingEnd="@dimen/standard_16" + android:paddingVertical="@dimen/mini_4"> 4dp 60dp + 60dp 12dp 60dp 5dp diff --git a/app/src/main/res/values/typography.xml b/app/src/main/res/values/typography.xml index fc9127f41e..ed7ac620eb 100644 --- a/app/src/main/res/values/typography.xml +++ b/app/src/main/res/values/typography.xml @@ -90,6 +90,10 @@ @dimen/sp12 + + From d6918db619caf67c40ea4a28acb0295a64cc52e7 Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:08:07 -0700 Subject: [PATCH 20/32] Create new activity for handling Transfer Requests --- app/src/main/AndroidManifest.xml | 4 + .../repository/PreferenceRepositoryType.java | 2 +- .../app/router/SendTokenRouter.java | 42 +- .../app/router/TransferRequestRouter.java | 20 + .../alphawallet/app/ui/AddTokenActivity.java | 4 +- .../app/ui/DappBrowserFragment.java | 2 +- .../app/ui/Erc20DetailActivity.java | 2 +- .../app/ui/TransferRequestActivity.java | 623 ++++++++++++++++++ .../app/viewmodel/BaseNavigationActivity.java | 5 +- .../app/viewmodel/DappBrowserViewModel.java | 19 +- .../app/viewmodel/Erc20DetailViewModel.java | 5 +- .../app/viewmodel/NFTInfoViewModel.java | 9 - .../app/viewmodel/NFTViewModel.java | 9 - .../res/layout/activity_transfer_request.xml | 74 +++ .../app/router/CoinbasePayRouterTest.java | 2 - 15 files changed, 754 insertions(+), 68 deletions(-) create mode 100644 app/src/main/java/com/alphawallet/app/router/TransferRequestRouter.java create mode 100644 app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java create mode 100644 app/src/main/res/layout/activity_transfer_request.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 799f59ecb3..7e27005bfc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -377,6 +377,10 @@ android:name=".ui.NotificationSettingsActivity" android:label="@string/title_notifications" /> + + getLastSentToken(); diff --git a/app/src/main/java/com/alphawallet/app/router/SendTokenRouter.java b/app/src/main/java/com/alphawallet/app/router/SendTokenRouter.java index 4292521d98..31138dc662 100644 --- a/app/src/main/java/com/alphawallet/app/router/SendTokenRouter.java +++ b/app/src/main/java/com/alphawallet/app/router/SendTokenRouter.java @@ -1,39 +1,39 @@ package com.alphawallet.app.router; - import android.app.Activity; import android.content.Intent; import com.alphawallet.app.C; -import com.alphawallet.app.entity.QRResult; -import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.ui.SendActivity; -public class SendTokenRouter { - public void open(Activity context, String address, String symbol, int decimals, Wallet wallet, Token token, long chainId) { +public class SendTokenRouter +{ + + // From token detail page + public void open(Activity context, Token token) + { Intent intent = new Intent(context, SendActivity.class); - intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, address); - intent.putExtra(C.EXTRA_ADDRESS, token.getAddress()); - intent.putExtra(C.EXTRA_NETWORKID, chainId); - intent.putExtra(C.EXTRA_SYMBOL, symbol); - intent.putExtra(C.EXTRA_DECIMALS, decimals); - intent.putExtra(C.Key.WALLET, wallet); - intent.putExtra(C.EXTRA_AMOUNT, (QRResult)null); + intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, token.getAddress()); + intent.putExtra(C.EXTRA_NETWORKID, token.tokenInfo.chainId); intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); context.startActivityForResult(intent, C.COMPLETED_TRANSACTION); } - // TODO: Check if this could be further refactored - public void open(Activity context, Wallet wallet, Token token) { + // From address scan + public void open(Activity context, String recipientAddress) + { + //TODO Implement + Intent intent = new Intent(context, SendActivity.class); + intent.putExtra(C.EXTRA_ADDRESS, recipientAddress); + intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + context.startActivityForResult(intent, C.COMPLETED_TRANSACTION); + } + + // From bottom nav + public void open(Activity context) + { Intent intent = new Intent(context, SendActivity.class); - intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, wallet.address); - intent.putExtra(C.EXTRA_ADDRESS, token.getAddress()); - intent.putExtra(C.EXTRA_NETWORKID, token.tokenInfo.chainId); - intent.putExtra(C.EXTRA_SYMBOL, token.getSymbol()); - intent.putExtra(C.EXTRA_DECIMALS, token.tokenInfo.decimals); - intent.putExtra(C.Key.WALLET, wallet); - intent.putExtra(C.EXTRA_AMOUNT, (QRResult)null); intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); context.startActivityForResult(intent, C.COMPLETED_TRANSACTION); } diff --git a/app/src/main/java/com/alphawallet/app/router/TransferRequestRouter.java b/app/src/main/java/com/alphawallet/app/router/TransferRequestRouter.java new file mode 100644 index 0000000000..8f6dca6ced --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/router/TransferRequestRouter.java @@ -0,0 +1,20 @@ +package com.alphawallet.app.router; + + +import android.app.Activity; +import android.content.Intent; + +import com.alphawallet.app.C; +import com.alphawallet.app.entity.QRResult; +import com.alphawallet.app.ui.TransferRequestActivity; + +public class TransferRequestRouter +{ + public void open(Activity context, QRResult result) + { + Intent intent = new Intent(context, TransferRequestActivity.class); + intent.putExtra(C.EXTRA_AMOUNT, result); + intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + context.startActivityForResult(intent, C.COMPLETED_TRANSACTION); + } +} diff --git a/app/src/main/java/com/alphawallet/app/ui/AddTokenActivity.java b/app/src/main/java/com/alphawallet/app/ui/AddTokenActivity.java index 93a136864d..77963ea4f0 100644 --- a/app/src/main/java/com/alphawallet/app/ui/AddTokenActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/AddTokenActivity.java @@ -249,14 +249,14 @@ private void finishAndLaunchSend() } else { - viewModel.showSend(this, currentResult, token); + viewModel.showSend(this, currentResult); finish(); } } else { //launch send payment screen for eth transaction - viewModel.showSend(this, currentResult, viewModel.getToken(currentResult.chainId, viewModel.wallet().getValue().address)); + viewModel.showSend(this, currentResult); finish(); } } 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 ad88271363..19e34cf3e5 100644 --- a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java @@ -1560,7 +1560,7 @@ public void handleQRCode(int resultCode, Intent data, FragmentMessenger messenge viewModel.track(Analytics.Action.SCAN_QR_CODE_SUCCESS, props); //EIP681 payment request scanned, should go to send - viewModel.showSend(getContext(), result); + viewModel.showSend(getActivity(), result); break; case FUNCTION_CALL: diff --git a/app/src/main/java/com/alphawallet/app/ui/Erc20DetailActivity.java b/app/src/main/java/com/alphawallet/app/ui/Erc20DetailActivity.java index c22994dd96..063d9c5407 100644 --- a/app/src/main/java/com/alphawallet/app/ui/Erc20DetailActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/Erc20DetailActivity.java @@ -425,7 +425,7 @@ public void handleTokenScriptFunction(String function, List selectio @Override public void showSend() { - viewModel.showSendToken(this, wallet, token); + viewModel.showSendToken(this, token); } @Override diff --git a/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java b/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java new file mode 100644 index 0000000000..6b053993ff --- /dev/null +++ b/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java @@ -0,0 +1,623 @@ +package com.alphawallet.app.ui; + +import static com.alphawallet.app.widget.AWalletAlertDialog.ERROR; +import static com.alphawallet.app.widget.AWalletAlertDialog.WARNING; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.view.MenuItem; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; +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.EIP681Type; +import com.alphawallet.app.entity.ErrorEnvelope; +import com.alphawallet.app.entity.GasEstimate; +import com.alphawallet.app.entity.Operation; +import com.alphawallet.app.entity.QRResult; +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.tokens.Token; +import com.alphawallet.app.service.GasService; +import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; +import com.alphawallet.app.ui.widget.entity.AddressReadyCallback; +import com.alphawallet.app.ui.widget.entity.AmountReadyCallback; +import com.alphawallet.app.util.Utils; +import com.alphawallet.app.viewmodel.SendViewModel; +import com.alphawallet.app.web3.entity.Address; +import com.alphawallet.app.web3.entity.Web3Transaction; +import com.alphawallet.app.widget.AWalletAlertDialog; +import com.alphawallet.app.widget.ActionSheetDialog; +import com.alphawallet.app.widget.FunctionButtonBar; +import com.alphawallet.app.widget.InputAddress; +import com.alphawallet.app.widget.InputAmount; +import com.alphawallet.app.widget.SignTransactionDialog; +import com.alphawallet.hardware.SignatureFromKey; +import com.alphawallet.token.tools.Convert; +import com.alphawallet.token.tools.Numeric; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.Collections; + +import dagger.hilt.android.AndroidEntryPoint; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; +import timber.log.Timber; + +@AndroidEntryPoint +public class TransferRequestActivity extends BaseActivity implements + AmountReadyCallback, + StandardFunctionInterface, + AddressReadyCallback, + ActionSheetCallback +{ + private static final BigDecimal NEGATIVE = BigDecimal.ZERO.subtract(BigDecimal.ONE); + private final Handler handler = new Handler(); + private SendViewModel viewModel; + private Wallet wallet; + private Token token; + private AWalletAlertDialog dialog; + private AWalletAlertDialog progressDialog; + private QRResult result; + private InputAmount amountInput; + private InputAddress addressInput; + private FunctionButtonBar functionBar; + private String sendAddress = null; + private String ensAddress; + private BigDecimal sendAmount = NEGATIVE; + private BigDecimal sendGasPrice = BigDecimal.ZERO; + private ActionSheetDialog confirmationDialog; + private final ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + result -> confirmationDialog.setCurrentGasIndex(result)); + @Nullable + private Disposable calcGasCost; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_transfer_request); + + toolbar(); + + setTitle(R.string.empty); + + initViews(); + + initViewModel(); + + result = getIntent().getParcelableExtra(C.EXTRA_AMOUNT); + + evaluateQrResult(result); + } + + private void initViews() + { + amountInput = findViewById(R.id.input_amount); + addressInput = findViewById(R.id.input_address); + functionBar = findViewById(R.id.layoutButtons); + + addressInput.setAddressCallback(this); + amountInput.hideCaret(); + + //TODO Send: set uneditable amount and address input. + amountInput.setEnabled(false); +// addressInput.setEnabled(false); + addressInput.setEditable(false); + addressInput.showControls(false); + + progressDialog = new AWalletAlertDialog(this); + progressDialog.setTitle(R.string.searching_for_token); + progressDialog.setIcon(AWalletAlertDialog.NONE); + progressDialog.setProgressMode(); + progressDialog.setCancelable(false); + } + + private void initViewModel() + { + viewModel = new ViewModelProvider(this).get(SendViewModel.class); + viewModel.wallet().observe(this, this::onWallet); + viewModel.finalisedToken().observe(this, this::onFinalisedToken); + viewModel.transactionFinalised().observe(this, this::txWritten); + viewModel.transactionError().observe(this, this::txError); + viewModel.error().observe(this, this::onError); + viewModel.progress().observe(this, this::showProgress); + } + + private void evaluateQrResult(QRResult r) + { + if (r != null) + { + this.result = r; + viewModel.prepare(); + } + else + { + displayScanError(); + finish(); + } + } + + private void onWallet(Wallet wallet) + { + this.wallet = wallet; + + if (!isValidChain(result.chainId)) + { + finish(); + } + else if (result.type == EIP681Type.PAYMENT) + { + token = viewModel.getToken(result.chainId, wallet.address); + } + else if (result.type == EIP681Type.TRANSFER) + { + token = viewModel.getToken(result.chainId, result.getAddress()); + } + + if (token != null) + { + evaluateToken(token); + } + else + { + Timber.d("You don't have this token, attempt to fetch"); + showProgress(true); + viewModel.fetchToken(result.chainId, result.getAddress(), wallet.address); + } + } + + private void evaluateToken(Token token) + { + if (token != null && token.balance.equals(BigDecimal.ZERO)) + { + displayToast("Wallet does not have requested token"); + showProgress(false); + finish(); + } + else if (token != null && token.isERC20()) + { + setupTokenContent(token); + addressInput.setAddress(result.functionToAddress); + amountInput.setAmount(result.tokenAmount.toString()); + amountInput.getInputAmount(); + } + else if (token != null && token.isEthereum()) + { + setupTokenContent(token); + addressInput.setAddress(result.getAddress()); + amountInput.setAmount(Convert.getConvertedValue(new BigDecimal(result.weiValue), Convert.Unit.ETHER.getFactor())); + amountInput.getInputAmount(); + } + else // TODO: Handle NFT + { + displayToast("NFTs not supported yet."); + finish(); + } + } + + private boolean isValidChain(long chainId) + { + if (viewModel.getNetworkInfo(chainId) == null) + { + displayToast(getString(R.string.chain_not_support, String.valueOf(chainId))); + return false; + } + if (!viewModel.isNetworkEnabled(chainId)) + { + displayToast("Network not enabled"); + return false; + } + return true; + } + + private void onFinalisedToken(Token token) + { + evaluateToken(token); + } + +// private void onTokens(List tokens) +// { +// // Filter tokens here. +// selectTokenDialog = new SelectTokenDialog(tokens, this, this); +// selectTokenDialog.show(); +// } + + private void setupTokenContent(Token token) + { + this.token = token; + amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); + addressInput.setChainOverrideForWalletConnect(token.tokenInfo.chainId); + + setTitle(getString(R.string.action_send_tkn, token.getSymbol())); + + functionBar.revealButtons(); + functionBar.setupFunctions(this, new ArrayList<>(Collections.singletonList(R.string.action_next))); + viewModel.startGasCycle(token.tokenInfo.chainId); + } + + private void onError(ErrorEnvelope errorEnvelope) + { + displayToast(errorEnvelope.message); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + if (item.getItemId() == android.R.id.home) + { + onBackPressed(); + } + return false; + } + + @Override + public void onBackPressed() + { + finish(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) + { + { + Operation taskCode = null; + if (requestCode >= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS && requestCode <= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS + 10) + { + taskCode = Operation.values()[requestCode - SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS]; + requestCode = SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS; + } + if (requestCode >= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS && requestCode <= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS + 10) + { + if (confirmationDialog != null && confirmationDialog.isShowing()) + confirmationDialog.completeSignRequest(resultCode == RESULT_OK); + } + else + { + super.onActivityResult(requestCode, resultCode, data); + } + } + } + + 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 displayScanError() + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(AWalletAlertDialog.ERROR); + dialog.setTitle(R.string.toast_qr_code_no_address); + dialog.setButtonText(R.string.dialog_cancel_back); + dialog.setButtonListener(v -> dialog.dismiss()); + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + } + + private void displayError(int titleId, String message) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(AWalletAlertDialog.ERROR); + dialog.setTitle(titleId); + dialog.setMessage(message); + dialog.setButtonText(R.string.dialog_ok); + dialog.setButtonListener(v -> dialog.dismiss()); + dialog.show(); + } + + @Override + protected void onDestroy() + { + if (dialog != null && dialog.isShowing()) + { + dialog.dismiss(); + } + super.onDestroy(); + if (viewModel != null) viewModel.onDestroy(); + if (handler != null) handler.removeCallbacksAndMessages(null); + if (amountInput != null) amountInput.onDestroy(); + if (confirmationDialog != null) confirmationDialog.onDestroy(); + } + + @Override + public void amountReady(BigDecimal value, BigDecimal gasPrice) + { + if (isBalanceSufficient(value)) + { + sendAmount = value; + sendGasPrice = gasPrice; + calculateTransactionCost(); + } + else + { + sendAmount = NEGATIVE; + amountInput.showError(true, 0); + addressInput.stopNameCheck(); + } + } + + private boolean isBalanceSufficient(BigDecimal value) + { + return (token.isEthereum() && token.balance.subtract(value).compareTo(BigDecimal.ZERO) > 0) // if sending base ethereum then check we have more than just the value + || (token.getBalanceRaw().subtract(value).compareTo(BigDecimal.ZERO) >= 0); + } + + @Override + public void handleClick(String action, int actionId) + { + if (actionId == R.string.action_next) + { + amountInput.getInputAmount(); + addressInput.getAddress(); + } + } + + @Override + public void addressReady(String address, String ensName) + { + sendAddress = address; + ensAddress = ensName; + if (!Utils.isAddressValid(address)) + { + //show address error + addressInput.setError(getString(R.string.error_invalid_address)); + } + else + { + calculateTransactionCost(); + } + } + + private void calculateTransactionCost() + { + if ((calcGasCost != null && !calcGasCost.isDisposed()) || + (confirmationDialog != null && confirmationDialog.isShowing())) return; + + if (sendAmount.compareTo(NEGATIVE) > 0 && Utils.isAddressValid(sendAddress)) + { + final String txSendAddress = sendAddress; + sendAddress = null; + //either sending base chain or ERC20 tokens. + final byte[] transactionBytes = viewModel.getTransactionBytes(token, txSendAddress, sendAmount); + + final String txDestAddress = token.isEthereum() ? txSendAddress : token.getAddress(); //either another address, or ERC20 Token address + + calculateEstimateDialog(); + //form payload and calculate tx cost + calcGasCost = viewModel.calculateGasEstimate(wallet, transactionBytes, token.tokenInfo.chainId, txDestAddress, BigDecimal.ZERO) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(estimate -> checkConfirm(estimate, transactionBytes, txDestAddress, txSendAddress), + error -> handleError(error, transactionBytes, token.getAddress(), txSendAddress)); + } + } + + private void handleError(Throwable throwable, final byte[] transactionBytes, final String txSendAddress, final String resolvedAddress) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + displayErrorMessage(throwable.getMessage()); + } + + private void checkConfirm(GasEstimate estimate, final byte[] transactionBytes, final String txSendAddress, final String resolvedAddress) + { + BigInteger ethValue = token.isEthereum() ? sendAmount.toBigInteger() : BigInteger.ZERO; + long leafCode = amountInput.isSendAll() ? -2 : -1; + Web3Transaction w3tx = new Web3Transaction( + new Address(txSendAddress), + token.isEthereum() ? null : new Address(token.getAddress()), + new Address(wallet.address), + ethValue, + sendGasPrice.toBigInteger(), + estimate.getValue(), + -1, + Numeric.toHexString(transactionBytes), + leafCode); + + if (estimate.hasError() || estimate.getValue().equals(BigInteger.ZERO)) + { + estimateError(estimate, w3tx, transactionBytes, txSendAddress, resolvedAddress); + } + else + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + confirmationDialog = new ActionSheetDialog(this, w3tx, token, ensAddress, + resolvedAddress, viewModel.getTokenService(), this); + confirmationDialog.setCanceledOnTouchOutside(false); + confirmationDialog.show(); + sendAmount = NEGATIVE; + } + } + + @Override + public void getAuthorisation(SignAuthenticationCallback callback) + { + viewModel.getAuthentication(this, wallet, callback); + } + + @Override + public void sendTransaction(Web3Transaction finalTx) + { + viewModel.requestSignature(finalTx, wallet, token.tokenInfo.chainId); + } + + @Override + public void completeSendTransaction(Web3Transaction tx, SignatureFromKey signature) + { + viewModel.sendTransaction(wallet, token.tokenInfo.chainId, tx, signature); + } + + @Override + public void dismissed(String txHash, long callbackId, boolean actionCompleted) + { + if (!TextUtils.isEmpty(txHash)) + { + Intent intent = new Intent(); + intent.putExtra(C.EXTRA_TXHASH, txHash); + setResult(RESULT_OK, intent); + + finish(); + } + } + + @Override + public ActivityResultLauncher gasSelectLauncher() + { + return getGasSettings; + } + + @Override + public void notifyConfirm(String mode) + { + AnalyticsProperties props = new AnalyticsProperties(); + props.put(Analytics.PROPS_ACTION_SHEET_MODE, mode); + viewModel.track(Analytics.Action.ACTION_SHEET_COMPLETED, props); + } + + @Override + public WalletType getWalletType() + { + return wallet.type; + } + + private void txWritten(TransactionReturn txData) + { + confirmationDialog.transactionWritten(txData.hash); + viewModel.setLastSentToken(token); + } + + private void txError(TransactionReturn txError) + { + Timber.e(txError.throwable); + if (txError.throwable instanceof SocketTimeoutException) + { + showTxnTimeoutDialog(); + } + else + { + showTxnErrorDialog(txError.throwable); + } + confirmationDialog.dismiss(); + } + + private void estimateError(GasEstimate estimate, final Web3Transaction w3tx, final byte[] transactionBytes, final String txSendAddress, final String resolvedAddress) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(WARNING); + dialog.setTitle(estimate.hasError() ? + R.string.dialog_title_gas_estimation_failed : + R.string.confirm_transaction + ); + String message = estimate.hasError() ? + getString(R.string.dialog_message_gas_estimation_failed, estimate.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 -> { + BigInteger gasEstimate = GasService.getDefaultGasLimit(token, w3tx); + checkConfirm(new GasEstimate(gasEstimate), transactionBytes, txSendAddress, resolvedAddress); + }); + + dialog.setSecondaryButtonListener(v -> { + dialog.dismiss(); + }); + + dialog.show(); + } + + void showTxnErrorDialog(Throwable t) + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(ERROR); + dialog.setTitle(R.string.error_transaction_failed); + dialog.setMessage(t.getMessage()); + dialog.setButtonText(R.string.button_ok); + dialog.setButtonListener(v -> { + dialog.dismiss(); + }); + dialog.show(); + } + + void showTxnTimeoutDialog() + { + if (dialog != null && dialog.isShowing()) dialog.dismiss(); + dialog = new AWalletAlertDialog(this); + dialog.setIcon(WARNING); + dialog.setTitle(R.string.error_transaction_timeout); + dialog.setMessage(R.string.message_transaction_timeout); + dialog.setButton(R.string.ok, v -> { + dialog.dismiss(); + }); + dialog.show(); + } + +// private void showChainChangeDialog(long chainId) +// { +// if (dialog != null && dialog.isShowing()) dialog.dismiss(); +// +// token = viewModel.getToken(chainId, wallet.address); +// +// dialog = new AWalletAlertDialog(this); +// dialog.setIcon(AWalletAlertDialog.WARNING); +// dialog.setTitle(R.string.change_chain_request); +// dialog.setMessage(R.string.change_chain_message); +// dialog.setButtonText(R.string.dialog_ok); +// dialog.setButtonListener(v -> { +// //we should change the chain. +// token = viewModel.getToken(chainId, token.getAddress()); +// amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); +// dialog.dismiss(); +// validateEIP681Request(currentResult, false); +// }); +// dialog.setSecondaryButtonText(R.string.action_cancel); +// dialog.setSecondaryButtonListener(v -> { +// dialog.dismiss(); +// //proceed without changing the chain +// currentResult.chainId = token.tokenInfo.chainId; +// validateEIP681Request(currentResult, false); +// }); +// dialog.show(); +// } + + private void showProgress(Boolean showProgress) + { + if (progressDialog != null) + { + if (showProgress) + { + progressDialog.show(); + } + else + { + progressDialog.dismiss(); + } + } + } +} diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java b/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java index f5692973af..0a67274150 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/BaseNavigationActivity.java @@ -5,6 +5,7 @@ import com.alphawallet.app.R; import com.alphawallet.app.entity.WalletPage; +import com.alphawallet.app.router.SendTokenRouter; import com.alphawallet.app.ui.BaseActivity; import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.widget.AWalletBottomNavigationView; @@ -16,9 +17,7 @@ protected void initBottomNavigation() { nav = findViewById(R.id.nav); nav.setListener(this); nav.setSendButtonListener(v -> { - Intent intent = new Intent(this, SendActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); + new SendTokenRouter().open(this); }); } diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java index a386d0a51d..9fb42649db 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java @@ -31,6 +31,7 @@ import com.alphawallet.app.interact.CreateTransactionInteract; import com.alphawallet.app.interact.GenericWalletInteract; import com.alphawallet.app.repository.EthereumNetworkRepositoryType; +import com.alphawallet.app.router.TransferRequestRouter; import com.alphawallet.app.service.AnalyticsServiceType; import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.GasService; @@ -42,7 +43,6 @@ import com.alphawallet.app.ui.ImportTokenActivity; import com.alphawallet.app.ui.MyAddressActivity; import com.alphawallet.app.ui.QRScanning.QRScannerActivity; -import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.util.DappBrowserUtils; @@ -267,22 +267,9 @@ public void failedAuthentication(Operation signData) keyService.failedAuthentication(signData); } - public void showSend(Context ctx, QRResult result) + public void showSend(Activity ctx, QRResult result) { - Intent intent = new Intent(ctx, SendActivity.class); - boolean sendingTokens = (result.getFunction() != null && result.getFunction().length() > 0); - String address = defaultWallet.getValue().address; - int decimals = 18; - - intent.putExtra(C.EXTRA_SENDING_TOKENS, sendingTokens); - intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, address); - intent.putExtra(C.EXTRA_NETWORKID, result.chainId); - intent.putExtra(C.EXTRA_SYMBOL, ethereumNetworkRepository.getNetworkByChain(result.chainId).symbol); - intent.putExtra(C.EXTRA_DECIMALS, decimals); - intent.putExtra(C.Key.WALLET, defaultWallet.getValue()); - intent.putExtra(C.EXTRA_AMOUNT, result); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - ctx.startActivity(intent); + new TransferRequestRouter().open(ctx, result); } public void requestSignature(Web3Transaction finalTx, Wallet wallet, long chainId) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java index 1a7fe54bd7..a7bff4ff4d 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/Erc20DetailViewModel.java @@ -101,12 +101,11 @@ public AssetDefinitionService getAssetDefinitionService() return this.assetDefinitionService; } - public void showSendToken(Activity act, Wallet wallet, Token token) + public void showSendToken(Activity act, Token token) { if (token != null) { - new SendTokenRouter().open(act, wallet.address, token.getSymbol(), token.tokenInfo.decimals, - wallet, token, token.tokenInfo.chainId); + new SendTokenRouter().open(act, token); } } diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/NFTInfoViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/NFTInfoViewModel.java index 3d6ad92f79..85f07aa211 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/NFTInfoViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/NFTInfoViewModel.java @@ -65,15 +65,6 @@ public AssetDefinitionService getAssetDefinitionService() return this.assetDefinitionService; } - public void showSendToken(Activity act, Wallet wallet, Token token) - { - if (token != null) - { - new SendTokenRouter().open(act, wallet.address, token.getSymbol(), token.tokenInfo.decimals, - wallet, token, token.tokenInfo.chainId); - } - } - public void checkTokenScriptValidity(Token token) { disposable = assetDefinitionService.getSignatureData(token.tokenInfo.chainId, token.tokenInfo.address) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/NFTViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/NFTViewModel.java index 7d4f1095d2..76a7606729 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/NFTViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/NFTViewModel.java @@ -96,15 +96,6 @@ public AssetDefinitionService getAssetDefinitionService() return this.assetDefinitionService; } - public void showSendToken(Activity act, Wallet wallet, Token token) - { - if (token != null) - { - new SendTokenRouter().open(act, wallet.address, token.getSymbol(), token.tokenInfo.decimals, - wallet, token, token.tokenInfo.chainId); - } - } - public Realm getRealmInstance(Wallet wallet) { return tokensService.getRealmInstance(wallet); diff --git a/app/src/main/res/layout/activity_transfer_request.xml b/app/src/main/res/layout/activity_transfer_request.xml new file mode 100644 index 0000000000..809b2362a4 --- /dev/null +++ b/app/src/main/res/layout/activity_transfer_request.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/test/java/com/alphawallet/app/router/CoinbasePayRouterTest.java b/app/src/test/java/com/alphawallet/app/router/CoinbasePayRouterTest.java index 38235a88a7..ba321a6ac6 100644 --- a/app/src/test/java/com/alphawallet/app/router/CoinbasePayRouterTest.java +++ b/app/src/test/java/com/alphawallet/app/router/CoinbasePayRouterTest.java @@ -6,11 +6,9 @@ import android.content.Intent; -import androidx.test.core.app.ActivityScenario; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.alphawallet.app.ui.CoinbasePayActivity; -import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.ui.SplashActivity; import com.alphawallet.shadows.ShadowApp; import com.alphawallet.shadows.ShadowKeyProviderFactory; From 3445d37e7e5c74cbce7146a0f780897805c03672 Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:08:49 -0700 Subject: [PATCH 21/32] Use transfer request router --- .../app/viewmodel/AddTokenViewModel.java | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/AddTokenViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/AddTokenViewModel.java index b821d5b5be..3cd9c3d09c 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/AddTokenViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/AddTokenViewModel.java @@ -1,5 +1,6 @@ package com.alphawallet.app.viewmodel; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Handler; @@ -20,10 +21,10 @@ import com.alphawallet.app.interact.FetchTransactionsInteract; import com.alphawallet.app.interact.GenericWalletInteract; import com.alphawallet.app.repository.EthereumNetworkRepositoryType; +import com.alphawallet.app.router.TransferRequestRouter; import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.TokensService; import com.alphawallet.app.ui.ImportTokenActivity; -import com.alphawallet.app.ui.SendActivity; import java.math.BigDecimal; import java.util.ArrayList; @@ -208,28 +209,9 @@ public void prepare() findWallet(); } - public void showSend(Context ctx, QRResult result, Token token) + public void showSend(Activity ctx, QRResult result) { - Intent intent = new Intent(ctx, SendActivity.class); - boolean sendingTokens = (result.getFunction() != null && result.getFunction().length() > 0); - String address = wallet.getValue().address; - int decimals = 18; - - if (sendingTokens) - { - address = result.getAddress(); - decimals = token.tokenInfo.decimals; - } - - intent.putExtra(C.EXTRA_SENDING_TOKENS, sendingTokens); - intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, address); - intent.putExtra(C.EXTRA_NETWORKID, token.tokenInfo.chainId); - intent.putExtra(C.EXTRA_SYMBOL, ethereumNetworkRepository.getNetworkByChain(result.chainId).symbol); - intent.putExtra(C.EXTRA_DECIMALS, decimals); - intent.putExtra(C.Key.WALLET, wallet.getValue()); - intent.putExtra(C.EXTRA_AMOUNT, result); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - ctx.startActivity(intent); + new TransferRequestRouter().open(ctx, result); } private List getNetworkIds() From 365a6d0b08cc91452412ecc049db9cb1ce2bc51b Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:17:17 -0700 Subject: [PATCH 22/32] Use appropriate routers --- .../app/viewmodel/HomeViewModel.java | 26 +++---------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java index dcd4ab4cee..1de7d7ee54 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java @@ -49,10 +49,12 @@ import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.repository.TokenRepository; import com.alphawallet.app.repository.entity.RealmWCSession; +import com.alphawallet.app.router.TransferRequestRouter; import com.alphawallet.app.router.ExternalBrowserRouter; import com.alphawallet.app.router.ImportTokenRouter; import com.alphawallet.app.router.MyAddressRouter; import com.alphawallet.app.service.AlphaWalletNotificationService; +import com.alphawallet.app.router.SendTokenRouter; import com.alphawallet.app.service.AnalyticsServiceType; import com.alphawallet.app.service.AssetDefinitionService; import com.alphawallet.app.service.RealmManager; @@ -62,7 +64,6 @@ import com.alphawallet.app.ui.AddTokenActivity; import com.alphawallet.app.ui.HomeActivity; import com.alphawallet.app.ui.ImportWalletActivity; -import com.alphawallet.app.ui.SendActivity; import com.alphawallet.app.util.QRParser; import com.alphawallet.app.util.RateApp; import com.alphawallet.app.util.Utils; @@ -379,8 +380,7 @@ public void handleQRCode(Activity activity, String qrCode) case TRANSFER: props.put(QrScanResultType.KEY, QrScanResultType.ADDRESS_OR_EIP_681.getValue()); track(Analytics.Action.SCAN_QR_CODE_SUCCESS, props); - - showSend(activity, qrResult); + new TransferRequestRouter().open(activity, qrResult); break; case FUNCTION_CALL: props.put(QrScanResultType.KEY, QrScanResultType.ADDRESS_OR_EIP_681.getValue()); @@ -425,7 +425,7 @@ private void showActionSheet(Activity activity, QRResult qrResult) View.OnClickListener listener = v -> { if (v.getId() == R.id.send_to_this_address_action) { - showSend(activity, qrResult); + new SendTokenRouter().open(activity, qrResult.getAddress()); } else if (v.getId() == R.id.add_custom_token_action) { @@ -481,24 +481,6 @@ else if (v.getId() == R.id.close_action) dialog.show(); } - public void showSend(Activity ctx, QRResult result) - { - Intent intent = new Intent(ctx, SendActivity.class); - boolean sendingTokens = (result.getFunction() != null && result.getFunction().length() > 0); - String address = defaultWallet.getValue().address; - int decimals = 18; - - intent.putExtra(C.EXTRA_SENDING_TOKENS, sendingTokens); - intent.putExtra(C.EXTRA_CONTRACT_ADDRESS, address); - intent.putExtra(C.EXTRA_NETWORKID, result.chainId); - intent.putExtra(C.EXTRA_SYMBOL, ethereumNetworkRepository.getNetworkByChain(result.chainId).symbol); - intent.putExtra(C.EXTRA_DECIMALS, decimals); - intent.putExtra(C.Key.WALLET, defaultWallet.getValue()); - intent.putExtra(C.EXTRA_AMOUNT, result); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - ctx.startActivity(intent); - } - public void showMyAddress(Activity activity) { myAddressRouter.open(activity, defaultWallet.getValue()); From 2d36cb5d5d88aa7f57958cdc7cc4e1c91254c2d2 Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:20:48 -0700 Subject: [PATCH 23/32] Rename Tokens class --- .../main/java/com/alphawallet/app/ui/SwapActivity.java | 8 ++++---- .../app/viewmodel/{Tokens.java => LifiTokenUtils.java} | 2 +- ...MappingTest.java => LifiTokenUtilsMappingTest.java} | 5 +++-- .../{TokensTest.java => LifiTokenUtilsTest.java} | 10 +++++----- 4 files changed, 13 insertions(+), 12 deletions(-) rename app/src/main/java/com/alphawallet/app/viewmodel/{Tokens.java => LifiTokenUtils.java} (97%) rename app/src/test/java/com/alphawallet/app/entity/{TokensMappingTest.java => LifiTokenUtilsMappingTest.java} (93%) rename app/src/test/java/com/alphawallet/app/viewmodel/{TokensTest.java => LifiTokenUtilsTest.java} (92%) diff --git a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java index a916c9c977..9ea73676b6 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java @@ -38,7 +38,7 @@ import com.alphawallet.app.util.BalanceUtils; import com.alphawallet.app.util.SwapUtils; import com.alphawallet.app.viewmodel.SwapViewModel; -import com.alphawallet.app.viewmodel.Tokens; +import com.alphawallet.app.viewmodel.LifiTokenUtils; import com.alphawallet.app.web3.entity.Web3Transaction; import com.alphawallet.app.widget.AWalletAlertDialog; import com.alphawallet.app.widget.ActionSheetDialog; @@ -420,7 +420,7 @@ private void initSourceToken(LifiToken selectedToken) private void initFromDialog(List fromTokens) { - Tokens.sortValue(fromTokens); + LifiTokenUtils.sortValue(fromTokens); sourceTokenDialog = new SelectLifiTokenDialog(fromTokens, this, tokenItem -> { sourceSelector.init(tokenItem); sourceTokenDialog.dismiss(); @@ -429,8 +429,8 @@ private void initFromDialog(List fromTokens) private void initToDialog(List toTokens) { - Tokens.sortName(toTokens); - Tokens.sortValue(toTokens); + LifiTokenUtils.sortName(toTokens); + LifiTokenUtils.sortValue(toTokens); destTokenDialog = new SelectLifiTokenDialog(toTokens, this, tokenItem -> { destSelector.init(tokenItem); destTokenDialog.dismiss(); diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java b/app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java similarity index 97% rename from app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java rename to app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java index e5c00da294..2a6592e75e 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/Tokens.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java @@ -7,7 +7,7 @@ import java.util.Collections; import java.util.List; -public class Tokens +public class LifiTokenUtils { public static void sortValue(List tokenItems) { diff --git a/app/src/test/java/com/alphawallet/app/entity/TokensMappingTest.java b/app/src/test/java/com/alphawallet/app/entity/LifiTokenUtilsMappingTest.java similarity index 93% rename from app/src/test/java/com/alphawallet/app/entity/TokensMappingTest.java rename to app/src/test/java/com/alphawallet/app/entity/LifiTokenUtilsMappingTest.java index df579b54ce..baad86c554 100644 --- a/app/src/test/java/com/alphawallet/app/entity/TokensMappingTest.java +++ b/app/src/test/java/com/alphawallet/app/entity/LifiTokenUtilsMappingTest.java @@ -15,11 +15,12 @@ import java.util.Collection; @RunWith(Parameterized.class) -public class TokensMappingTest { +public class LifiTokenUtilsMappingTest +{ private String groupString; private TokenGroup groupEnum; - public TokensMappingTest(String groupString, TokenGroup groupEnum) { + public LifiTokenUtilsMappingTest(String groupString, TokenGroup groupEnum) { this.groupString = groupString; this.groupEnum = groupEnum; } diff --git a/app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java b/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java similarity index 92% rename from app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java rename to app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java index 9c2afa5b67..9841645d62 100644 --- a/app/src/test/java/com/alphawallet/app/viewmodel/TokensTest.java +++ b/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java @@ -10,7 +10,7 @@ import java.util.ArrayList; import java.util.List; -public class TokensTest +public class LifiTokenUtilsTest { @Test public void sort_token_by_fiat_value_in_DESC() @@ -20,7 +20,7 @@ public void sort_token_by_fiat_value_in_DESC() list.add(createToken("Binance Smart Chain", "BNB", "0x1", 1)); list.add(createToken("Solana", "SOL", "0x2", 2)); - Tokens.sortValue(list); + LifiTokenUtils.sortValue(list); assertThat(list.get(0).symbol, equalTo("SOL")); assertThat(list.get(1).symbol, equalTo("BNB")); @@ -35,7 +35,7 @@ public void sort_tokens_by_name_alphabetically() list.add(createToken("Binance Smart Chain", "BNB", "0x1", 0)); list.add(createToken("Solana", "SOL", "0x2", 0)); - Tokens.sortName(list); + LifiTokenUtils.sortName(list); assertThat(list.get(0).symbol, equalTo("BNB")); assertThat(list.get(1).symbol, equalTo("ETH")); @@ -50,7 +50,7 @@ public void sort_name_should_return_native_token_first() list.add(createToken("Stox", "STX", "0x0000000000000000000000000000000000000000", 0)); list.add(createToken("stETH", "stETH", "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 0)); - Tokens.sortName(list); + LifiTokenUtils.sortName(list); assertThat(list.get(0).symbol, equalTo("stETH")); assertThat(list.get(1).symbol, equalTo("STX")); @@ -64,7 +64,7 @@ public void sort_name_should_be_case_insensitive() list.add(createToken("Stox", "STX", "0x0", 0)); list.add(createToken("stETH", "stETH", "0x3", 0)); - Tokens.sortName(list); + LifiTokenUtils.sortName(list); assertThat(list.get(0).symbol, equalTo("stETH")); assertThat(list.get(1).symbol, equalTo("STX")); From fbc33436026fd066a9309fac851850d0b176550f Mon Sep 17 00:00:00 2001 From: justindg Date: Wed, 10 May 2023 23:21:33 -0700 Subject: [PATCH 24/32] Move to utils package --- app/src/main/java/com/alphawallet/app/ui/SwapActivity.java | 2 +- .../com/alphawallet/app/{viewmodel => util}/LifiTokenUtils.java | 2 +- .../java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) rename app/src/main/java/com/alphawallet/app/{viewmodel => util}/LifiTokenUtils.java (96%) diff --git a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java index 9ea73676b6..e07e102d7c 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SwapActivity.java @@ -38,7 +38,7 @@ import com.alphawallet.app.util.BalanceUtils; import com.alphawallet.app.util.SwapUtils; import com.alphawallet.app.viewmodel.SwapViewModel; -import com.alphawallet.app.viewmodel.LifiTokenUtils; +import com.alphawallet.app.util.LifiTokenUtils; import com.alphawallet.app.web3.entity.Web3Transaction; import com.alphawallet.app.widget.AWalletAlertDialog; import com.alphawallet.app.widget.ActionSheetDialog; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java b/app/src/main/java/com/alphawallet/app/util/LifiTokenUtils.java similarity index 96% rename from app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java rename to app/src/main/java/com/alphawallet/app/util/LifiTokenUtils.java index 2a6592e75e..84809670bb 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/LifiTokenUtils.java +++ b/app/src/main/java/com/alphawallet/app/util/LifiTokenUtils.java @@ -1,4 +1,4 @@ -package com.alphawallet.app.viewmodel; +package com.alphawallet.app.util; import com.alphawallet.app.entity.lifi.LifiToken; diff --git a/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java b/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java index 9841645d62..747149a7c7 100644 --- a/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java +++ b/app/src/test/java/com/alphawallet/app/viewmodel/LifiTokenUtilsTest.java @@ -4,6 +4,7 @@ import static org.hamcrest.core.IsEqual.equalTo; import com.alphawallet.app.entity.lifi.LifiToken; +import com.alphawallet.app.util.LifiTokenUtils; import org.junit.Test; From 09401f79fe71c025d01d52a90a62b780e61077e4 Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 11 May 2023 00:04:05 -0700 Subject: [PATCH 25/32] Rename and move to utils package --- .../adapter/SelectLifiTokenAdapter.java | 7 ++++--- .../LifiTokenFilter.java} | 6 +++--- .../widget/adapter/LifiTokenFilterTest.java | 21 ++++++++++--------- 3 files changed, 18 insertions(+), 16 deletions(-) rename app/src/main/java/com/alphawallet/app/{ui/widget/adapter/TokenFilter.java => util/LifiTokenFilter.java} (94%) diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java index f66db64187..84f4145dc9 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectLifiTokenAdapter.java @@ -11,6 +11,7 @@ import com.alphawallet.app.R; import com.alphawallet.app.entity.lifi.LifiToken; +import com.alphawallet.app.util.LifiTokenFilter; import com.alphawallet.app.widget.AddressIcon; import com.alphawallet.app.widget.SelectLifiTokenDialog; import com.google.android.material.radiobutton.MaterialRadioButton; @@ -22,12 +23,12 @@ public class SelectLifiTokenAdapter extends RecyclerView.Adapter displayData; private final SelectLifiTokenDialog.EventListener callback; - private final TokenFilter tokenFilter; + private final LifiTokenFilter lifiTokenFilter; private String selectedTokenAddress; public SelectLifiTokenAdapter(List tokens, SelectLifiTokenDialog.EventListener callback) { - tokenFilter = new TokenFilter(tokens); + lifiTokenFilter = new LifiTokenFilter(tokens); this.callback = callback; displayData = new ArrayList<>(); displayData.addAll(tokens); @@ -76,7 +77,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) public void filter(String keyword) { - updateList(tokenFilter.filterBy(keyword)); + updateList(lifiTokenFilter.filterBy(keyword)); } public void updateList(List filteredList) diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java b/app/src/main/java/com/alphawallet/app/util/LifiTokenFilter.java similarity index 94% rename from app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java rename to app/src/main/java/com/alphawallet/app/util/LifiTokenFilter.java index 224681ceca..34b1e59e1c 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/TokenFilter.java +++ b/app/src/main/java/com/alphawallet/app/util/LifiTokenFilter.java @@ -1,4 +1,4 @@ -package com.alphawallet.app.ui.widget.adapter; +package com.alphawallet.app.util; import android.text.TextUtils; @@ -11,11 +11,11 @@ import java.util.ListIterator; import java.util.Locale; -public class TokenFilter +public class LifiTokenFilter { private final List tokens; - public TokenFilter(List tokens) + public LifiTokenFilter(List tokens) { this.tokens = tokens; removeBadTokens(); diff --git a/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java b/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java index 99a8174d09..e43bcec822 100644 --- a/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java +++ b/app/src/test/java/com/alphawallet/app/ui/widget/adapter/LifiTokenFilterTest.java @@ -6,6 +6,7 @@ import androidx.annotation.NonNull; import com.alphawallet.app.entity.lifi.LifiToken; +import com.alphawallet.app.util.LifiTokenFilter; import org.junit.Before; import org.junit.Test; @@ -15,7 +16,7 @@ public class LifiTokenFilterTest { - private TokenFilter tokenFilter; + private LifiTokenFilter lifiTokenFilter; @Before public void setUp() throws Exception @@ -25,13 +26,13 @@ public void setUp() throws Exception list.add(createToken("Solana", "SOL", "2")); list.add(createToken("Binance", "BNB", "3")); list.add(createToken("", "", "4")); - tokenFilter = new TokenFilter(list); + lifiTokenFilter = new LifiTokenFilter(list); } @Test public void nameContains() { - List result = tokenFilter.filterBy("an"); + List result = lifiTokenFilter.filterBy("an"); assertThat(result.size(), equalTo(2)); assertThat(result.get(0).name, equalTo("Solana")); assertThat(result.get(1).name, equalTo("Binance")); @@ -40,7 +41,7 @@ public void nameContains() @Test public void nameStartsWith() { - List result = tokenFilter.filterBy("So"); + List result = lifiTokenFilter.filterBy("So"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); } @@ -48,7 +49,7 @@ public void nameStartsWith() @Test public void symbolContains() { - List result = tokenFilter.filterBy("B"); + List result = lifiTokenFilter.filterBy("B"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Binance")); } @@ -56,7 +57,7 @@ public void symbolContains() @Test public void symbolStartsWith() { - List result = tokenFilter.filterBy("S"); + List result = lifiTokenFilter.filterBy("S"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); } @@ -64,11 +65,11 @@ public void symbolStartsWith() @Test public void should_be_case_insensitive() { - List result = tokenFilter.filterBy("s"); + List result = lifiTokenFilter.filterBy("s"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Solana")); - result = tokenFilter.filterBy("b"); + result = lifiTokenFilter.filterBy("b"); assertThat(result.size(), equalTo(1)); assertThat(result.get(0).name, equalTo("Binance")); } @@ -80,9 +81,9 @@ public void should_sort_starts_with_in_front_of_contains() list.add(createToken("Solana", "SOL", "2")); list.add(createToken("WETH", "WETH", "2")); list.add(createToken("Ethereum", "ETH", "1")); - tokenFilter = new TokenFilter(list); + lifiTokenFilter = new LifiTokenFilter(list); - List result = tokenFilter.filterBy("eth"); + List result = lifiTokenFilter.filterBy("eth"); assertThat(result.size(), equalTo(2)); assertThat(result.get(0).name, equalTo("Ethereum")); assertThat(result.get(1).name, equalTo("WETH")); From 96cb98711ddae083b340bb6b1f25adf7663ce2ac Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 11 May 2023 00:12:58 -0700 Subject: [PATCH 26/32] Apply changes to SendActivity --- .../com/alphawallet/app/ui/SendActivity.java | 278 +++++++----------- .../ui/widget/adapter/SelectTokenAdapter.java | 12 +- .../{TokenFilter2.java => TokenFilter.java} | 12 +- .../app/viewmodel/SendViewModel.java | 113 ++++++- 4 files changed, 229 insertions(+), 186 deletions(-) rename app/src/main/java/com/alphawallet/app/util/{TokenFilter2.java => TokenFilter.java} (81%) diff --git a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java index e3c7479eb6..7596338b3f 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java @@ -1,9 +1,7 @@ package com.alphawallet.app.ui; -import static com.alphawallet.app.C.Key.WALLET; import static com.alphawallet.app.widget.AWalletAlertDialog.ERROR; import static com.alphawallet.app.widget.AWalletAlertDialog.WARNING; -import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; import android.app.Activity; import android.content.Intent; @@ -12,8 +10,6 @@ import android.text.TextUtils; import android.view.Menu; import android.view.MenuItem; -import android.view.View; -import android.widget.TextView; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; @@ -25,9 +21,8 @@ import com.alphawallet.app.analytics.Analytics; import com.alphawallet.app.entity.AnalyticsProperties; import com.alphawallet.app.entity.CryptoFunctions; -import com.alphawallet.app.entity.EIP681Type; +import com.alphawallet.app.entity.ErrorEnvelope; import com.alphawallet.app.entity.GasEstimate; -import com.alphawallet.app.entity.NetworkInfo; import com.alphawallet.app.entity.Operation; import com.alphawallet.app.entity.QRResult; import com.alphawallet.app.entity.SignAuthenticationCallback; @@ -38,6 +33,7 @@ import com.alphawallet.app.entity.tokens.Token; import com.alphawallet.app.repository.EthereumNetworkBase; import com.alphawallet.app.repository.EthereumNetworkRepository; +import com.alphawallet.app.router.TransferRequestRouter; import com.alphawallet.app.service.GasService; import com.alphawallet.app.ui.QRScanning.QRScannerActivity; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; @@ -54,10 +50,10 @@ import com.alphawallet.app.widget.FunctionButtonBar; import com.alphawallet.app.widget.InputAddress; import com.alphawallet.app.widget.InputAmount; +import com.alphawallet.app.widget.SelectTokenDialog; import com.alphawallet.app.widget.SignTransactionDialog; import com.alphawallet.hardware.SignatureFromKey; import com.alphawallet.token.entity.SalesOrderMalformed; -import com.alphawallet.token.tools.Convert; import com.alphawallet.token.tools.Numeric; import com.alphawallet.token.tools.ParseMagicLink; @@ -75,28 +71,31 @@ import timber.log.Timber; @AndroidEntryPoint -public class SendActivity extends BaseActivity implements AmountReadyCallback, StandardFunctionInterface, AddressReadyCallback, ActionSheetCallback +public class SendActivity extends BaseActivity implements + AmountReadyCallback, + StandardFunctionInterface, + AddressReadyCallback, + ActionSheetCallback, + SelectTokenDialog.OnTokenClickListener { private static final BigDecimal NEGATIVE = BigDecimal.ZERO.subtract(BigDecimal.ONE); - - SendViewModel viewModel; - + private final Handler handler = new Handler(); + private SendViewModel viewModel; private Wallet wallet; private Token token; - private final Handler handler = new Handler(); private AWalletAlertDialog dialog; - - private QRResult currentResult; - private InputAmount amountInput; private InputAddress addressInput; - private String sendAddress; + private FunctionButtonBar functionBar; + private String sendAddress = null; private String ensAddress; - private BigDecimal sendAmount; - private BigDecimal sendGasPrice; + private BigDecimal sendAmount = NEGATIVE; + private BigDecimal sendGasPrice = BigDecimal.ZERO; private ActionSheetDialog confirmationDialog; + private final ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), + result -> confirmationDialog.setCurrentGasIndex(result)); + private SelectTokenDialog selectTokenDialog; private AWalletAlertDialog alertDialog; - @Nullable private Disposable calcGasCost; @@ -104,68 +103,76 @@ public class SendActivity extends BaseActivity implements AmountReadyCallback, S protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + setContentView(R.layout.activity_send); + toolbar(); - viewModel = new ViewModelProvider(this) - .get(SendViewModel.class); + setTitle("Send"); - String contractAddress = getIntent().getStringExtra(C.EXTRA_CONTRACT_ADDRESS); - long currentChain = getIntent().getLongExtra(C.EXTRA_NETWORKID, MAINNET_ID); - wallet = getIntent().getParcelableExtra(WALLET); - token = viewModel.getToken(currentChain, getIntent().getStringExtra(C.EXTRA_ADDRESS)); - QRResult result = getIntent().getParcelableExtra(C.EXTRA_AMOUNT); + initViewModel(); - viewModel.transactionFinalised().observe(this, this::txWritten); - viewModel.transactionError().observe(this, this::txError); + initViews(); - sendAddress = null; - sendGasPrice = BigDecimal.ZERO; - sendAmount = NEGATIVE; +// evaluateQrResult(getIntent().getParcelableExtra(C.EXTRA_AMOUNT)); - if (!checkTokenValidity(currentChain, contractAddress)) - { - return; - } + viewModel.prepare(); + } - setTitle(getString(R.string.action_send_tkn, token.getShortName())); - setupTokenContent(); + private void initViews() + { + amountInput = findViewById(R.id.input_amount); + addressInput = findViewById(R.id.input_address); + functionBar = findViewById(R.id.layoutButtons); - if (result != null) - { - //restore payment request - validateEIP681Request(result, true); - } + amountInput.setListener(v -> viewModel.fetchTokens()); + addressInput.setAddressCallback(this); } - @Override - protected void onResume() + private void initViewModel() + { + viewModel = new ViewModelProvider(this) + .get(SendViewModel.class); + viewModel.wallet().observe(this, this::onWallet); + viewModel.tokens().observe(this, this::onTokens); + viewModel.transactionFinalised().observe(this, this::txWritten); + viewModel.transactionError().observe(this, this::txError); + viewModel.error().observe(this, this::onError); + } + + private void onWallet(Wallet wallet) { - super.onResume(); + this.wallet = wallet; - QRResult result = getIntent().getParcelableExtra(C.EXTRA_AMOUNT); + String recipientAddress = getIntent().getStringExtra(C.EXTRA_ADDRESS); + String tokenAddress = getIntent().getStringExtra(C.EXTRA_CONTRACT_ADDRESS); + long tokenChainId = getIntent().getLongExtra(C.EXTRA_NETWORKID, -1); - if (result != null && (result.type == EIP681Type.PAYMENT || result.type == EIP681Type.TRANSFER)) + if (!TextUtils.isEmpty(recipientAddress)) // From address qr + { + addressInput.setAddress(recipientAddress); + viewModel.fetchTokens(); + } + else if (!TextUtils.isEmpty(tokenAddress) && tokenChainId != -1) // From token detail + { + token = viewModel.getToken(tokenChainId, tokenAddress); + setupTokenContent(token); + } + else // From bottom nav { - handleClick("", R.string.action_next); + viewModel.fetchTokens(); } } - private boolean checkTokenValidity(long currentChain, String contractAddress) + private void onTokens(List tokens) { - if (token == null || token.tokenInfo == null) - { - //bad token - try to load from service - token = viewModel.getToken(currentChain, contractAddress); - - if (token == null) - { - //TODO: possibly invoke token finder in tokensService - finish(); - } - } + selectTokenDialog = new SelectTokenDialog(tokens, this, this); + selectTokenDialog.show(); + } - return (token != null); + private void onError(ErrorEnvelope errorEnvelope) + { + displayToast(errorEnvelope.message); } private void onBack() @@ -284,7 +291,7 @@ else if (requestCode == C.BARCODE_READER_REQUEST_CODE) break; default: Timber.tag("SEND").e(String.format(getString(R.string.barcode_error_format), - "Code: " + resultCode + "Code: " + resultCode )); break; } @@ -344,109 +351,26 @@ private void calculateEstimateDialog() private void validateEIP681Request(QRResult result, boolean overrideNetwork) { - if (dialog != null) dialog.dismiss(); - //check chain - if (result == null) - { - displayScanError(); - return; - } - - NetworkInfo info = viewModel.getNetworkInfo(result.chainId); - if (info == null) - { - displayScanError(); - return; - } - else if (result.type != EIP681Type.ADDRESS && result.chainId != token.tokenInfo.chainId && token.isEthereum()) - { - //Display chain change warning - currentResult = result; - showChainChangeDialog(result.chainId); - return; - } - - TextView sendText = findViewById(R.id.text_payment_request); - switch (result.type) { case ADDRESS: addressInput.setAddress(result.getAddress()); break; - case PAYMENT: - //correct chain and asset type - String ethAmount = Convert.getConvertedValue(new BigDecimal(result.weiValue), Convert.Unit.ETHER.getFactor()); - sendText.setVisibility(View.VISIBLE); - sendText.setText(R.string.transfer_request); - token = viewModel.getToken(result.chainId, wallet.address); - addressInput.setAddress(result.getAddress()); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); - amountInput.setAmount(ethAmount); - setupTokenContent(); - break; - case TRANSFER: - Token resultToken = viewModel.getToken(result.chainId, result.getAddress()); - if (resultToken == null) - { - currentResult = result; - showTokenFetch(); - viewModel.fetchToken(result.chainId, result.getAddress(), wallet.address); - } - else if (resultToken.isERC20()) - { - //ERC20 send request - token = resultToken; - setupTokenContent(); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); - //convert token amount into scaled value - String convertedAmount = Convert.getConvertedValue(result.tokenAmount, token.tokenInfo.decimals); - amountInput.setAmount(convertedAmount); - addressInput.setAddress(result.functionToAddress); - sendText.setVisibility(View.VISIBLE); - sendText.setText(getString(R.string.token_transfer_request, resultToken.getFullName())); - } - //TODO: Handle NFT eg ERC721 + new TransferRequestRouter().open(this, result); break; - case FUNCTION_CALL: //Generic function call, not handled yet displayScanError(R.string.toast_qr_code_no_address, getString(R.string.no_tokens)); if (result.functionToAddress != null) addressInput.setAddress(result.functionToAddress); break; - default: displayScanError(); } } - private void showChainChangeDialog(long chainId) - { - if (dialog != null && dialog.isShowing()) dialog.dismiss(); - dialog = new AWalletAlertDialog(this); - dialog.setIcon(AWalletAlertDialog.WARNING); - dialog.setTitle(R.string.change_chain_request); - dialog.setMessage(R.string.change_chain_message); - dialog.setButtonText(R.string.dialog_ok); - dialog.setButtonListener(v -> { - //we should change the chain. - token = viewModel.getToken(chainId, token.getAddress()); - amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); - dialog.dismiss(); - validateEIP681Request(currentResult, false); - }); - dialog.setSecondaryButtonText(R.string.action_cancel); - dialog.setSecondaryButtonListener(v -> { - dialog.dismiss(); - //proceed without changing the chain - currentResult.chainId = token.tokenInfo.chainId; - validateEIP681Request(currentResult, false); - }); - dialog.show(); - } - private void displayScanError() { if (dialog != null && dialog.isShowing()) dialog.dismiss(); @@ -486,20 +410,19 @@ protected void onDestroy() // addressInput.setEnsNodeNotSyncCallback(null); // prevent leak by removing reference to activity method } - private void setupTokenContent() + private void setupTokenContent(Token token) { - amountInput = findViewById(R.id.input_amount); + this.token = token; + setTitle(getString(R.string.action_send_tkn, token.getSymbol())); amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); - addressInput = findViewById(R.id.input_address); - addressInput.setAddressCallback(this); addressInput.setChainOverrideForWalletConnect(token.tokenInfo.chainId); //addressInput.setEnsHandlerNodeSyncFlag(true); // allow node sync //addressInput.setEnsNodeNotSyncCallback(this::showNodeNotSyncSheet); // callback to invoke if node not synced - FunctionButtonBar functionBar = findViewById(R.id.layoutButtons); functionBar.revealButtons(); List functions = new ArrayList<>(Collections.singletonList(R.string.action_next)); functionBar.setupFunctions(this, functions); viewModel.startGasCycle(token.tokenInfo.chainId); + amountInput.focus(); } @Override @@ -507,7 +430,7 @@ public void amountReady(BigDecimal value, BigDecimal gasPrice) { //validate that we have sufficient balance if ((token.isEthereum() && token.balance.subtract(value).compareTo(BigDecimal.ZERO) > 0) // if sending base ethereum then check we have more than just the value - || (token.getBalanceRaw().subtract(value).compareTo(BigDecimal.ZERO) >= 0)) // contract token, check sufficient token balance (gas widget will check sufficient gas) + || (token.getBalanceRaw().subtract(value).compareTo(BigDecimal.ZERO) >= 0)) // contract token, check sufficient token balance (gas widget will check sufficient gas) { sendAmount = value; sendGasPrice = gasPrice; @@ -554,7 +477,7 @@ public void addressReady(String address, String ensName) private void calculateTransactionCost() { if ((calcGasCost != null && !calcGasCost.isDisposed()) || - (confirmationDialog != null && confirmationDialog.isShowing())) return; + (confirmationDialog != null && confirmationDialog.isShowing())) return; if (sendAmount.compareTo(NEGATIVE) > 0 && Utils.isAddressValid(sendAddress)) { @@ -568,10 +491,10 @@ private void calculateTransactionCost() calculateEstimateDialog(); //form payload and calculate tx cost calcGasCost = viewModel.calculateGasEstimate(wallet, transactionBytes, token.tokenInfo.chainId, txDestAddress, BigDecimal.ZERO) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(estimate -> checkConfirm(estimate, transactionBytes, txDestAddress, txSendAddress), - error -> handleError(error, transactionBytes, token.getAddress(), txSendAddress)); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(estimate -> checkConfirm(estimate, transactionBytes, txDestAddress, txSendAddress), + error -> handleError(error, transactionBytes, token.getAddress(), txSendAddress)); } } @@ -589,15 +512,15 @@ private void checkConfirm(final GasEstimate estimate, final byte[] transactionBy BigInteger ethValue = token.isEthereum() ? sendAmount.toBigInteger() : BigInteger.ZERO; long leafCode = amountInput.isSendAll() ? -2 : -1; Web3Transaction w3tx = new Web3Transaction( - new Address(txSendAddress), - token.isEthereum() ? null : new Address(token.getAddress()), - new Address(wallet.address), - ethValue, - sendGasPrice.toBigInteger(), - estimate.getValue(), - -1, - Numeric.toHexString(transactionBytes), - leafCode); + new Address(txSendAddress), + token.isEthereum() ? null : new Address(token.getAddress()), + new Address(wallet.address), + ethValue, + sendGasPrice.toBigInteger(), + estimate.getValue(), + -1, + Numeric.toHexString(transactionBytes), + leafCode); if (estimate.hasError() || estimate.getValue().equals(BigInteger.ZERO)) { @@ -607,7 +530,7 @@ private void checkConfirm(final GasEstimate estimate, final byte[] transactionBy { if (dialog != null && dialog.isShowing()) dialog.dismiss(); confirmationDialog = new ActionSheetDialog(this, w3tx, token, ensAddress, - resolvedAddress, viewModel.getTokenService(), this); + resolvedAddress, viewModel.getTokenService(), this); confirmationDialog.setCanceledOnTouchOutside(false); confirmationDialog.show(); sendAmount = NEGATIVE; @@ -652,9 +575,6 @@ public void dismissed(String txHash, long callbackId, boolean actionCompleted) } } - ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> confirmationDialog.setCurrentGasIndex(result)); - @Override public ActivityResultLauncher gasSelectLauncher() { @@ -678,6 +598,11 @@ public WalletType getWalletType() private void txWritten(TransactionReturn txData) { confirmationDialog.transactionWritten(txData.hash); + viewModel.setLastSentToken(token); +// viewModel.setLastSentTokenAddress(txData.tx.contract != null ? +// txData.tx.contract.toString() : +// txData.tx.sender.toString() +// ); } //Transaction failed to be sent @@ -703,7 +628,7 @@ private void estimateError(GasEstimate estimate, final Web3Transaction w3tx, fin dialog.setTitle(estimate.hasError() ? R.string.dialog_title_gas_estimation_failed : R.string.confirm_transaction - ); + ); String message = estimate.hasError() ? getString(R.string.dialog_message_gas_estimation_failed, estimate.getError()) : getString(R.string.error_transaction_may_fail); @@ -773,4 +698,15 @@ void showTxnTimeoutDialog() }); dialog.show(); } + + @Override + public void onTokenClicked(Token token) + { + if (selectTokenDialog != null && selectTokenDialog.isShowing()) + { + selectTokenDialog.dismiss(); + } + setupTokenContent(token); + Timber.d("juz here onTokenClicked"); + } } diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java index 97ea39213a..03ebdb99cc 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/adapter/SelectTokenAdapter.java @@ -1,5 +1,6 @@ package com.alphawallet.app.ui.widget.adapter; +import android.annotation.SuppressLint; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -11,7 +12,7 @@ import com.alphawallet.app.R; import com.alphawallet.app.entity.tokens.Token; -import com.alphawallet.app.util.TokenFilter2; +import com.alphawallet.app.util.TokenFilter; import com.alphawallet.app.widget.SelectTokenDialog; import com.alphawallet.app.widget.TokenIcon; @@ -21,13 +22,13 @@ public class SelectTokenAdapter extends RecyclerView.Adapter { private final List displayData; - private final TokenFilter2 tokenFilter; + private final TokenFilter tokenFilter; private final SelectTokenDialog.OnTokenClickListener listener; public SelectTokenAdapter(List tokens, SelectTokenDialog.OnTokenClickListener listener) { this.listener = listener; - tokenFilter = new TokenFilter2(tokens); + tokenFilter = new TokenFilter(tokens); displayData = new ArrayList<>(); displayData.addAll(tokens); } @@ -73,11 +74,12 @@ public void filter(String keyword) updateList(tokenFilter.filterBy(keyword)); } + @SuppressLint("NotifyDataSetChanged") public void updateList(List filteredList) { displayData.clear(); displayData.addAll(filteredList); - notifyItemRangeChanged(0, filteredList.size()); + notifyDataSetChanged(); } @Override @@ -102,6 +104,4 @@ static class ViewHolder extends RecyclerView.ViewHolder tokenIcon = view.findViewById(R.id.token_icon); } } - - } diff --git a/app/src/main/java/com/alphawallet/app/util/TokenFilter2.java b/app/src/main/java/com/alphawallet/app/util/TokenFilter.java similarity index 81% rename from app/src/main/java/com/alphawallet/app/util/TokenFilter2.java rename to app/src/main/java/com/alphawallet/app/util/TokenFilter.java index afd6f3ceaa..4664f4f4b4 100644 --- a/app/src/main/java/com/alphawallet/app/util/TokenFilter2.java +++ b/app/src/main/java/com/alphawallet/app/util/TokenFilter.java @@ -1,5 +1,7 @@ package com.alphawallet.app.util; +import android.text.TextUtils; + import androidx.annotation.NonNull; import com.alphawallet.app.entity.tokens.Token; @@ -8,17 +10,23 @@ import java.util.List; import java.util.Locale; -public class TokenFilter2 +public class TokenFilter { private final List tokens; - public TokenFilter2(List tokens) + private final List copy; + + public TokenFilter(List tokens) { this.tokens = tokens; + this.copy = new ArrayList<>(); + this.copy.addAll(tokens); } public List filterBy(String keyword) { + if (TextUtils.isEmpty(keyword)) return copy; + String lowerCaseKeyword = lowerCase(keyword); List result = new ArrayList<>(); diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/SendViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/SendViewModel.java index 9c91b3b9ca..89c74c1167 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/SendViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/SendViewModel.java @@ -3,7 +3,9 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.util.Pair; +import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import com.alphawallet.app.C; @@ -14,9 +16,13 @@ import com.alphawallet.app.entity.TransactionReturn; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.tokens.Token; +import com.alphawallet.app.entity.tokens.TokenCardMeta; import com.alphawallet.app.entity.tokens.TokenInfo; import com.alphawallet.app.interact.CreateTransactionInteract; +import com.alphawallet.app.interact.FetchTokensInteract; +import com.alphawallet.app.interact.GenericWalletInteract; import com.alphawallet.app.repository.EthereumNetworkRepositoryType; +import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.repository.TokenRepository; import com.alphawallet.app.router.MyAddressRouter; import com.alphawallet.app.service.AnalyticsServiceType; @@ -31,6 +37,10 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; import javax.inject.Inject; @@ -45,6 +55,8 @@ public class SendViewModel extends BaseViewModel implements TransactionSendHandl private final MutableLiveData finalisedToken = new MutableLiveData<>(); private final MutableLiveData transactionFinalised = new MutableLiveData<>(); private final MutableLiveData transactionError = new MutableLiveData<>(); + private final MutableLiveData wallet = new MutableLiveData<>(); + private final MutableLiveData> tokens = new MutableLiveData<>(); private final MyAddressRouter myAddressRouter; private final EthereumNetworkRepositoryType networkRepository; @@ -53,6 +65,9 @@ public class SendViewModel extends BaseViewModel implements TransactionSendHandl private final AssetDefinitionService assetDefinitionService; private final KeyService keyService; private final CreateTransactionInteract createTransactionInteract; + private final PreferenceRepositoryType preferenceRepository; + private final GenericWalletInteract genericWalletInteract; + private final FetchTokensInteract fetchTokensInteract; @Inject public SendViewModel(MyAddressRouter myAddressRouter, @@ -62,7 +77,10 @@ public SendViewModel(MyAddressRouter myAddressRouter, GasService gasService, AssetDefinitionService assetDefinitionService, KeyService keyService, - AnalyticsServiceType analyticsService) + AnalyticsServiceType analyticsService, + PreferenceRepositoryType preferenceRepository, + GenericWalletInteract genericWalletInteract, + FetchTokensInteract fetchTokensInteract) { this.myAddressRouter = myAddressRouter; this.networkRepository = ethereumNetworkRepositoryType; @@ -71,9 +89,85 @@ public SendViewModel(MyAddressRouter myAddressRouter, this.assetDefinitionService = assetDefinitionService; this.keyService = keyService; this.createTransactionInteract = createTransactionInteract; + this.preferenceRepository = preferenceRepository; + this.genericWalletInteract = genericWalletInteract; + this.fetchTokensInteract = fetchTokensInteract; setAnalyticsService(analyticsService); } + public void prepare() + { + disposable = genericWalletInteract.find() + .observeOn(Schedulers.io()) + .subscribeOn(Schedulers.io()) + .subscribe(wallet::postValue, this::onError); + } + + public void fetchTokens() + { + disposable = fetchTokensInteract.fetchTokenMetas(wallet.getValue(), tokensService.getNetworkFilters(), assetDefinitionService) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::onTokenMetas, this::onError); + } + + private void onTokenMetas(TokenCardMeta[] tokenCardMetas) + { + Arrays.sort(tokenCardMetas, Comparator.comparing(TokenCardMeta::getNameWeight)); + List tokenList = new ArrayList<>(); + int i = 0; + for (TokenCardMeta meta : tokenCardMetas) + { + Pair lastSentToken = getLastSentToken(); + Token t = tokensService.getToken(meta.getChain(), meta.getAddress()); + if (t.balance.compareTo(BigDecimal.ZERO) > 0 && !t.isNonFungible()) + { + boolean isLastSentToken = t.tokenInfo.address.equalsIgnoreCase(lastSentToken.first); + if (i == 0 && isLastSentToken && t.tokenInfo.chainId == lastSentToken.second) + { + tokenList.add(i++, t); // Insert at the top + } + else if (i > 0 && isLastSentToken) + { + // Will only trigger if last sent token is a network token + // List other network tokens below last sent + tokenList.add(i++, t); + } + else + { + tokenList.add(t); + } + } + } + + tokens.postValue(tokenList); + } + + public Pair getLastSentToken() + { + return preferenceRepository.getLastSentToken(); + } + + public void setLastSentToken(Token token) + { + preferenceRepository.setLastSentToken(token); + } + + public LiveData> tokens() + { + return tokens; + } + + public MutableLiveData wallet() + { + return wallet; + } + + public MutableLiveData finalisedToken() + { + return finalisedToken; + } + public MutableLiveData transactionFinalised() { return transactionFinalised; @@ -94,6 +188,11 @@ public NetworkInfo getNetworkInfo(long chainId) return networkRepository.getNetworkByChain(chainId); } + public boolean isNetworkEnabled(long chainId) + { + return networkRepository.getFilterNetworkList().contains(chainId); + } + public Token getToken(long chainId, String tokenAddress) { return tokensService.getToken(chainId, tokenAddress); @@ -110,17 +209,17 @@ public void showImportLink(Context context, String importTxt) public void fetchToken(long chainId, String address, String walletAddress) { tokensService.update(address, chainId, ContractType.NOT_SET) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(tokenInfo -> gotTokenUpdate(tokenInfo, walletAddress), this::onError).isDisposed(); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(tokenInfo -> gotTokenUpdate(tokenInfo, walletAddress), this::onError).isDisposed(); } private void gotTokenUpdate(TokenInfo tokenInfo, String walletAddress) { disposable = tokensService.addToken(tokenInfo, walletAddress) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(finalisedToken::postValue, this::onError); + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(finalisedToken::postValue, this::onError); } public AssetDefinitionService getAssetDefinitionService() From d72ab499c93141ff005f022324bf56d7e8f29c94 Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 11 May 2023 00:16:34 -0700 Subject: [PATCH 27/32] Add new methods to InputAddress --- .../com/alphawallet/app/widget/InputAddress.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/alphawallet/app/widget/InputAddress.java b/app/src/main/java/com/alphawallet/app/widget/InputAddress.java index f21de52ad8..898742e876 100644 --- a/app/src/main/java/com/alphawallet/app/widget/InputAddress.java +++ b/app/src/main/java/com/alphawallet/app/widget/InputAddress.java @@ -328,7 +328,11 @@ public void ENSResolved(String address, String ens) { errorText.setVisibility(View.GONE); setWaitingSpinner(false); - bindAvatar(address, ens); + if (!TextUtils.isEmpty(address) && !TextUtils.isEmpty(ens)) + { + bindAvatar(address, ens); + } + if (addressReadyCallback != null) { addressReadyCallback.resolvedAddress(address, ens); @@ -552,6 +556,16 @@ public long getChain() return chainOverride; } + public void showControls(boolean show) + { + pasteItem.setVisibility(show ? View.VISIBLE : View.GONE); + } + + public void setEditable(boolean editable) + { + editText.setEnabled(editable); + } + /*public void setEnsNodeNotSyncCallback(EnsNodeNotSyncCallback callback) { Timber.d("setEnsNodeNotSyncCallback: "); From 67944eeae6a096060d00b026f0da249a77b956d7 Mon Sep 17 00:00:00 2001 From: justindg Date: Thu, 11 May 2023 23:26:09 -0700 Subject: [PATCH 28/32] UI fixes/updates --- app/src/main/AndroidManifest.xml | 2 +- .../com/alphawallet/app/ui/SendActivity.java | 17 +- .../app/ui/TransferRequestActivity.java | 22 +- .../com/alphawallet/app/widget/ChainName.java | 18 +- .../alphawallet/app/widget/InputAmount.java | 264 ++++++++++++------ .../app/widget/StandardHeader.java | 12 +- .../res/drawable/background_chain_inverse.xml | 2 +- .../res/drawable/background_chain_name.xml | 2 +- app/src/main/res/layout/activity_send.xml | 12 - .../res/layout/activity_transfer_request.xml | 12 - .../main/res/layout/dialog_sign_method.xml | 1 - app/src/main/res/layout/item_chain_name.xml | 19 +- app/src/main/res/layout/item_input_amount.xml | 123 +++++--- app/src/main/res/layout/item_switch_chain.xml | 11 +- 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/dimens.xml | 1 + app/src/main/res/values/strings.xml | 3 +- 22 files changed, 331 insertions(+), 196 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e27005bfc..c16555c436 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -379,7 +379,7 @@ + android:label="@string/transfer_request" /> viewModel.fetchTokens()); addressInput.setAddressCallback(this); + amountInput.setListener(v -> showTokenSelectDialog()); } private void initViewModel() @@ -151,7 +151,7 @@ private void onWallet(Wallet wallet) if (!TextUtils.isEmpty(recipientAddress)) // From address qr { addressInput.setAddress(recipientAddress); - viewModel.fetchTokens(); + showTokenSelectDialog(); } else if (!TextUtils.isEmpty(tokenAddress) && tokenChainId != -1) // From token detail { @@ -160,14 +160,17 @@ else if (!TextUtils.isEmpty(tokenAddress) && tokenChainId != -1) // From token d } else // From bottom nav { - viewModel.fetchTokens(); + showTokenSelectDialog(); } } private void onTokens(List tokens) { selectTokenDialog = new SelectTokenDialog(tokens, this, this); - selectTokenDialog.show(); + if (!selectTokenDialog.isShowing()) + { + selectTokenDialog.show(); + } } private void onError(ErrorEnvelope errorEnvelope) @@ -699,6 +702,11 @@ void showTxnTimeoutDialog() dialog.show(); } + private void showTokenSelectDialog() + { + viewModel.fetchTokens(); + } + @Override public void onTokenClicked(Token token) { @@ -707,6 +715,5 @@ public void onTokenClicked(Token token) selectTokenDialog.dismiss(); } setupTokenContent(token); - Timber.d("juz here onTokenClicked"); } } diff --git a/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java b/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java index 6b053993ff..860f22f9c6 100644 --- a/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/TransferRequestActivity.java @@ -112,15 +112,11 @@ private void initViews() amountInput = findViewById(R.id.input_amount); addressInput = findViewById(R.id.input_address); functionBar = findViewById(R.id.layoutButtons); - addressInput.setAddressCallback(this); - amountInput.hideCaret(); - - //TODO Send: set uneditable amount and address input. - amountInput.setEnabled(false); -// addressInput.setEnabled(false); addressInput.setEditable(false); addressInput.showControls(false); + amountInput.setEditable(false); + amountInput.showControls(false); progressDialog = new AWalletAlertDialog(this); progressDialog.setTitle(R.string.searching_for_token); @@ -164,10 +160,12 @@ private void onWallet(Wallet wallet) } else if (result.type == EIP681Type.PAYMENT) { + setTitle(getString(R.string.title_payment_request)); token = viewModel.getToken(result.chainId, wallet.address); } else if (result.type == EIP681Type.TRANSFER) { + setTitle(getString(R.string.transfer_request)); token = viewModel.getToken(result.chainId, result.getAddress()); } @@ -177,7 +175,7 @@ else if (result.type == EIP681Type.TRANSFER) } else { - Timber.d("You don't have this token, attempt to fetch"); + // You don't have this token, attempt to fetch showProgress(true); viewModel.fetchToken(result.chainId, result.getAddress(), wallet.address); } @@ -232,21 +230,11 @@ private void onFinalisedToken(Token token) evaluateToken(token); } -// private void onTokens(List tokens) -// { -// // Filter tokens here. -// selectTokenDialog = new SelectTokenDialog(tokens, this, this); -// selectTokenDialog.show(); -// } - private void setupTokenContent(Token token) { this.token = token; amountInput.setupToken(token, viewModel.getAssetDefinitionService(), viewModel.getTokenService(), this); addressInput.setChainOverrideForWalletConnect(token.tokenInfo.chainId); - - setTitle(getString(R.string.action_send_tkn, token.getSymbol())); - functionBar.revealButtons(); functionBar.setupFunctions(this, new ArrayList<>(Collections.singletonList(R.string.action_next))); viewModel.startGasCycle(token.tokenInfo.chainId); diff --git a/app/src/main/java/com/alphawallet/app/widget/ChainName.java b/app/src/main/java/com/alphawallet/app/widget/ChainName.java index c7038ca512..83ff8453f1 100644 --- a/app/src/main/java/com/alphawallet/app/widget/ChainName.java +++ b/app/src/main/java/com/alphawallet/app/widget/ChainName.java @@ -2,9 +2,7 @@ import android.content.Context; import android.content.res.TypedArray; -import android.text.TextUtils; import android.util.AttributeSet; -import android.util.TypedValue; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; @@ -20,6 +18,7 @@ */ public class ChainName extends LinearLayout { + private final LinearLayout layout; private final TextView chainName; private boolean invertNameColour; @@ -27,6 +26,7 @@ public ChainName(Context context, @Nullable AttributeSet attrs) { super(context, attrs); inflate(context, R.layout.item_chain_name, this); + layout = findViewById(R.id.layout_chain_name); chainName = findViewById(R.id._text_chain_name); getAttrs(context, attrs); } @@ -41,12 +41,12 @@ public void setChainID(long chainId) if (invertNameColour) { chainName.setTextColor(getContext().getColor(EthereumNetworkBase.getChainColour(chainId))); - chainName.setBackgroundResource(R.drawable.background_chain_inverse); + layout.setBackgroundResource(R.drawable.background_chain_inverse); } else { chainName.setTextColor(getContext().getColor(R.color.white)); - chainName.getBackground().setTint(ContextCompat.getColor(getContext(), + layout.getBackground().setTint(ContextCompat.getColor(getContext(), EthereumNetworkBase.getChainColour(chainId))); } } @@ -59,15 +59,15 @@ public void setChainID(long chainId) private void getAttrs(Context context, AttributeSet attrs) { TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.InputView, - 0, 0 + attrs, + R.styleable.InputView, + 0, 0 ); try { - int fontSize = a.getInteger(R.styleable.InputView_font_size, 12); - chainName.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); +// int fontSize = a.getInteger(R.styleable.InputView_font_size, 12); +// chainName.setTextSize(TypedValue.COMPLEX_UNIT_SP, fontSize); invertNameColour = a.getBoolean(R.styleable.InputView_invert, false); } finally diff --git a/app/src/main/java/com/alphawallet/app/widget/InputAmount.java b/app/src/main/java/com/alphawallet/app/widget/InputAmount.java index 4d49ba8deb..6b214c2221 100644 --- a/app/src/main/java/com/alphawallet/app/widget/InputAmount.java +++ b/app/src/main/java/com/alphawallet/app/widget/InputAmount.java @@ -1,15 +1,21 @@ package com.alphawallet.app.widget; +import static com.alphawallet.app.C.GAS_LIMIT_MIN; +import static com.alphawallet.app.repository.TokensRealmSource.databaseKey; + import android.content.Context; import android.content.res.TypedArray; import android.os.Handler; import android.os.Looper; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.View; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; +import android.widget.RelativeLayout; import android.widget.TextView; import com.alphawallet.app.R; @@ -26,6 +32,7 @@ import com.alphawallet.app.ui.widget.entity.AmountReadyCallback; import com.alphawallet.app.ui.widget.entity.NumericInput; import com.alphawallet.app.util.BalanceUtils; +import com.google.android.material.button.MaterialButton; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,9 +48,6 @@ import io.realm.RealmQuery; import timber.log.Timber; -import static com.alphawallet.app.C.GAS_LIMIT_MIN; -import static com.alphawallet.app.repository.TokensRealmSource.databaseKey; - /** * Created by JB on 10/11/2020. */ @@ -54,9 +58,15 @@ public class InputAmount extends LinearLayout private final TextView symbolText; private final TokenIcon icon; private final StandardHeader header; - private final TextView availableSymbol; + private final RelativeLayout headerLayout; + private final ChainName chainName; private final TextView availableAmount; + private final TextView equivalent; private final TextView allFunds; + private final ImageView switchButton; + private final ImageView caret; + private final LinearLayout clickMore; + private final MaterialButton selectTokenButton; private final ProgressBar gasFetch; private Token token; private Realm realm; @@ -68,12 +78,12 @@ public class InputAmount extends LinearLayout private final Handler handler = new Handler(Looper.getMainLooper()); private AmountReadyCallback amountReadyCallback; private boolean amountReady; - + private boolean showingCrypto; + private boolean isEditable = true; //These need to be members because the listener is shut down if the object doesn't exist private RealmTokenTicker realmTickerUpdate; private RealmToken realmTokenUpdate; - private boolean showingCrypto; public InputAmount(Context context, AttributeSet attrs) { @@ -85,16 +95,50 @@ public InputAmount(Context context, AttributeSet attrs) symbolText = findViewById(R.id.text_token_symbol); icon = findViewById(R.id.token_icon); header = findViewById(R.id.header); - availableSymbol = findViewById(R.id.text_symbol); + headerLayout = findViewById(R.id.layout_header); + chainName = findViewById(R.id.chain); availableAmount = findViewById(R.id.text_available); allFunds = findViewById(R.id.text_all_funds); gasFetch = findViewById(R.id.gas_fetch_progress); + clickMore = findViewById(R.id.layout_more_click); + selectTokenButton = findViewById(R.id.btn_select_token); + switchButton = findViewById(R.id.btn_switch); + caret = findViewById(R.id.expand_more); + equivalent = findViewById(R.id.equivalent); showingCrypto = !CustomViewSettings.inputAmountFiatDefault(); amountReady = false; setupAttrs(context, attrs); + } - setupViewListeners(); + private void setupAttrs(Context context, AttributeSet attrs) + { + TypedArray a = context.getTheme().obtainStyledAttributes( + attrs, + R.styleable.InputView, + 0, 0 + ); + + try + { + boolean showHeader = a.getBoolean(R.styleable.InputView_show_header, true); + boolean showAllFunds = a.getBoolean(R.styleable.InputView_show_allFunds, true); + boolean showChainName = a.getBoolean(R.styleable.InputView_showChainName, true); + boolean currencyMode = a.getBoolean(R.styleable.InputView_currencyMode, false); + int headerTextId = a.getResourceId(R.styleable.InputView_label, R.string.amount); + headerLayout.setVisibility(showHeader ? View.VISIBLE : View.GONE); + allFunds.setVisibility(showAllFunds ? View.VISIBLE : View.GONE); + header.setText(headerTextId); + if (currencyMode) + { + symbolText.setText(TickerService.getCurrencySymbolTxt()); + icon.showLocalCurrency(); + } + } + finally + { + a.recycle(); + } } /** @@ -105,21 +149,34 @@ public InputAmount(Context context, AttributeSet attrs) * @param assetDefinitionService * @param svs */ - public void setupToken(@NotNull Token token, @Nullable AssetDefinitionService assetDefinitionService, - @NotNull TokensService svs, @NotNull AmountReadyCallback amountCallback) + public void setupToken(@NotNull Token token, + @Nullable AssetDefinitionService assetDefinitionService, + @NotNull TokensService svs, + @NotNull AmountReadyCallback amountCallback) { this.token = token; this.tokensService = svs; this.assetService = assetDefinitionService; this.amountReadyCallback = amountCallback; - icon.bindData(token, assetService); - header.getChainName().setChainID(token.tokenInfo.chainId); - updateAvailableBalance(); - this.realm = tokensService.getWalletRealmInstance(); this.tickerRealm = tokensService.getTickerRealmInstance(); + + selectTokenButton.setVisibility(View.GONE); + clickMore.setVisibility(View.VISIBLE); + chainName.setVisibility(View.VISIBLE); + chainName.setChainID(token.tokenInfo.chainId); + + icon.bindData(token, assetService); + bindDataSource(); + setupAllFunds(); + + setupViewListeners(); + + updateAvailableBalance(); + + updateEquivalent(); } public void getInputAmount() @@ -215,14 +272,14 @@ private void bindDataSource() if (realmTokenUpdate != null) realmTokenUpdate.removeAllChangeListeners(); realmTokenUpdate = realm.where(RealmToken.class) - .equalTo("address", databaseKey(token.tokenInfo.chainId, token.tokenInfo.address.toLowerCase()), Case.INSENSITIVE) - .findFirstAsync(); + .equalTo("address", databaseKey(token.tokenInfo.chainId, token.tokenInfo.address.toLowerCase()), Case.INSENSITIVE) + .findFirstAsync(); //if the token doesn't exist yet, first ask the TokensService to pick it up tokensService.storeToken(token); realmTokenUpdate.addChangeListener(realmToken -> { - RealmToken rt = (RealmToken)realmToken; + RealmToken rt = (RealmToken) realmToken; if (rt.isValid() && exactAmount.compareTo(BigDecimal.ZERO) == 0) { token = tokensService.getToken(rt.getChainId(), rt.getTokenAddress()); @@ -231,37 +288,59 @@ private void bindDataSource() }); } - private void setupViewListeners() + public void setListener(OnClickListener listener) { - LinearLayout clickMore = findViewById(R.id.layout_more_click); - - clickMore.setOnClickListener(v -> { - //on down caret clicked - switch to fiat currency equivalent if there's a ticker - if (getTickerQuery() == null) return; + if (listener != null) + { + caret.setVisibility(View.VISIBLE); + clickMore.setOnClickListener(listener); + selectTokenButton.setOnClickListener(listener); + } + else + { + caret.setVisibility(View.GONE); + } + } - RealmTokenTicker rtt = getTickerQuery().findFirst(); - if (showingCrypto && rtt != null) - { - showingCrypto = false; - startTickerListener(); - } - else - { - showingCrypto = true; - if (tickerRealm != null) tickerRealm.removeAllChangeListeners(); //stop ticker listener - } + private void setupViewListeners() + { + if (getTickerQuery() != null && isEditable) + { + switchButton.setVisibility(View.VISIBLE); + switchButton.setOnClickListener(v -> { + RealmTokenTicker rtt = getTickerQuery().findFirst(); + if (showingCrypto && rtt != null) + { + showingCrypto = false; + startTickerListener(); + } + else + { + showingCrypto = true; + if (tickerRealm != null) + tickerRealm.removeAllChangeListeners(); //stop ticker listener + } - updateAvailableBalance(); - }); + updateAvailableBalance(); + updateEquivalent(); + }); + } + else + { + switchButton.setVisibility(View.GONE); + } - editText.addTextChangedListener(new TextWatcher() { + editText.addTextChangedListener(new TextWatcher() + { @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { + public void beforeTextChanged(CharSequence s, int start, int count, int after) + { } @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { + public void onTextChanged(CharSequence s, int start, int before, int count) + { if (editText.hasFocus()) { exactAmount = BigDecimal.ZERO; //invalidate the 'all funds' amount @@ -270,7 +349,9 @@ public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override - public void afterTextChanged(Editable s) { + public void afterTextChanged(Editable s) + { + updateEquivalent(); if (editText.hasFocus()) { amountReadyCallback.updateCryptoAmount(getWeiInputAmount()); @@ -295,7 +376,7 @@ private RealmQuery getTickerQuery() if (tickerRealm != null) { return tickerRealm.where(RealmTokenTicker.class) - .equalTo("contract", TokensRealmSource.databaseKey(token.tokenInfo.chainId, token.isEthereum() ? "eth" : token.getAddress().toLowerCase())); + .equalTo("contract", TokensRealmSource.databaseKey(token.tokenInfo.chainId, token.isEthereum() ? "eth" : token.getAddress().toLowerCase())); } else { @@ -317,11 +398,40 @@ private void showCrypto() { icon.bindData(token, assetService); symbolText.setText(token.getSymbol()); - availableSymbol.setText(token.getSymbol()); - availableAmount.setText(token.getStringBalanceForUI(5)); + + availableAmount.setText(String.format("%s: %s %s", context.getString(R.string.balance), token.getStringBalanceForUI(5), token.getSymbol())); + updateAllFundsAmount(); } + private void updateEquivalent() + { + if (getTickerQuery() == null || TextUtils.isEmpty(getTickerQuery().findFirst().getPrice())) + { + equivalent.setVisibility(View.GONE); + switchButton.setVisibility(View.GONE); + return; + } + + double cryptoRate = Double.parseDouble(getTickerQuery().findFirst().getPrice()); + BigDecimal amount = editText.getBigDecimalValue(); + BigDecimal rate = new BigDecimal(cryptoRate); + + BigDecimal value; + if (showingCrypto) + { + value = amount.multiply(rate).setScale(2, RoundingMode.CEILING); + equivalent.setText(String.format("%s %s", getTickerQuery().findFirst().getCurrencySymbol(), value)); + } + else + { + value = amount.divide(rate, 4, RoundingMode.FLOOR); + equivalent.setText(String.format("%s %s", value, token.tokenInfo.symbol)); + } + + equivalent.setVisibility(View.VISIBLE); + } + private void showFiat() { icon.showLocalCurrency(); @@ -335,18 +445,18 @@ private void showFiat() if (rtt != null) { - String currencyLabel = rtt.getCurrencySymbol() + TickerService.getCurrencySymbol(); + String currencyLabel = rtt.getCurrencySymbol(); symbolText.setText(currencyLabel); //calculate available fiat double cryptoRate = Double.parseDouble(rtt.getPrice()); double availableCryptoBalance = token.getCorrectedBalance(18).doubleValue(); - availableAmount.setText(TickerService.getCurrencyString(availableCryptoBalance * cryptoRate)); - availableSymbol.setText(rtt.getCurrencySymbol()); + BigDecimal balance = new BigDecimal(cryptoRate).multiply(new BigDecimal(availableCryptoBalance)).setScale(2, RoundingMode.FLOOR); + + availableAmount.setText(String.format("%s: %s %s", context.getString(R.string.balance), balance.toString(), rtt.getCurrencySymbol())); + updateAllFundsAmount(); //update amount if showing 'All Funds' - amountReadyCallback.updateCryptoAmount( - getWeiInputAmount() - ); //now update + amountReadyCallback.updateCryptoAmount(getWeiInputAmount()); //now update } updateAllFundsAmount(); } @@ -384,8 +494,8 @@ private void setupAllFunds() if (token.isEthereum() && token.hasPositiveBalance()) { RealmGasSpread gasSpread = tokensService.getTickerRealmInstance().where(RealmGasSpread.class) - .equalTo("chainId", token.tokenInfo.chainId) - .findFirst(); + .equalTo("chainId", token.tokenInfo.chainId) + .findFirst(); if (gasSpread != null && gasSpread.getGasPrice().compareTo(BigInteger.ZERO) > 0) { @@ -397,8 +507,8 @@ private void setupAllFunds() gasFetch.setVisibility(View.VISIBLE); Web3j web3j = TokenRepository.getWeb3jService(token.tokenInfo.chainId); web3j.ethGasPrice().sendAsync() - .thenAccept(ethGasPrice -> onLatestGasPrice(ethGasPrice.getGasPrice())) - .exceptionally(this::onGasFetchError); + .thenAccept(ethGasPrice -> onLatestGasPrice(ethGasPrice.getGasPrice())) + .exceptionally(this::onGasFetchError); } } else @@ -446,37 +556,6 @@ public void run() } }; - private void setupAttrs(Context context, AttributeSet attrs) - { - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.InputView, - 0, 0 - ); - - try - { - boolean showHeader = a.getBoolean(R.styleable.InputView_show_header, true); - boolean showAllFunds = a.getBoolean(R.styleable.InputView_show_allFunds, true); - boolean showChainName = a.getBoolean(R.styleable.InputView_showChainName, true); - boolean currencyMode = a.getBoolean(R.styleable.InputView_currencyMode, false); - int headerTextId = a.getResourceId(R.styleable.InputView_label, R.string.amount); - header.setVisibility(showHeader ? View.VISIBLE : View.GONE); - allFunds.setVisibility(showAllFunds ? View.VISIBLE : View.GONE); - header.setText(headerTextId); - header.getChainName().setVisibility(showChainName ? View.VISIBLE : View.GONE); - if (currencyMode) - { - symbolText.setText(TickerService.getCurrencySymbolTxt()); - icon.showLocalCurrency(); - } - } - finally - { - a.recycle(); - } - } - private Void onGasFetchError(Throwable throwable) { gasFetch.setVisibility(View.GONE); @@ -553,4 +632,21 @@ public boolean isSendAll() { return exactAmount.compareTo(BigDecimal.ZERO) > 0; } + + public void focus() + { + editText.requestFocus(); + } + + public void showControls(boolean show) + { + switchButton.setVisibility(show ? View.VISIBLE : View.GONE); + allFunds.setVisibility(show ? View.VISIBLE : View.GONE); + } + + public void setEditable(boolean editable) + { + this.isEditable = editable; + editText.setEnabled(editable); + } } diff --git a/app/src/main/java/com/alphawallet/app/widget/StandardHeader.java b/app/src/main/java/com/alphawallet/app/widget/StandardHeader.java index 2cd0a27315..164de1a6a4 100644 --- a/app/src/main/java/com/alphawallet/app/widget/StandardHeader.java +++ b/app/src/main/java/com/alphawallet/app/widget/StandardHeader.java @@ -63,22 +63,30 @@ private void getAttrs(Context context, AttributeSet attrs) if (showTextControl) { textControl.setVisibility(View.VISIBLE); - textControl.setText(controlText); } else { textControl.setVisibility(View.GONE); } + if (controlText != -1) + { + textControl.setText(controlText); + } + if (showImageControl) { imageControl.setVisibility(View.VISIBLE); - imageControl.setImageResource(controlImageRes); } else { imageControl.setVisibility(View.GONE); } + + if (controlImageRes != -1) + { + imageControl.setImageResource(controlImageRes); + } } finally { diff --git a/app/src/main/res/drawable/background_chain_inverse.xml b/app/src/main/res/drawable/background_chain_inverse.xml index 74e5f5728c..8c0f89c2d2 100644 --- a/app/src/main/res/drawable/background_chain_inverse.xml +++ b/app/src/main/res/drawable/background_chain_inverse.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_chain_name.xml b/app/src/main/res/drawable/background_chain_name.xml index 926e786462..f78e8d3ac0 100644 --- a/app/src/main/res/drawable/background_chain_name.xml +++ b/app/src/main/res/drawable/background_chain_name.xml @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_send.xml b/app/src/main/res/layout/activity_send.xml index 594d82b07f..66cbd703fb 100644 --- a/app/src/main/res/layout/activity_send.xml +++ b/app/src/main/res/layout/activity_send.xml @@ -26,18 +26,6 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - - diff --git a/app/src/main/res/layout/item_chain_name.xml b/app/src/main/res/layout/item_chain_name.xml index ad0545b789..7d0ccf145a 100644 --- a/app/src/main/res/layout/item_chain_name.xml +++ b/app/src/main/res/layout/item_chain_name.xml @@ -1,21 +1,24 @@ + android:id="@+id/layout_chain_name" + android:background="@drawable/background_chain_name" + android:orientation="horizontal" + android:gravity="center" + android:paddingStart="@dimen/standard_16" + android:paddingEnd="@dimen/standard_16" + android:paddingVertical="@dimen/mini_4">