1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-17 19:10:51 +02:00

Compare commits

..

11 Commits

Author SHA1 Message Date
m2049r
2fbd152fb3 bump version 2021-03-13 13:16:26 +01:00
m2049r
a7b178e024 Tweaking UI & some bugfixing on the way (#729) 2021-03-13 13:12:46 +01:00
m2049r
c5a035437b bump version 2021-03-12 20:16:05 +01:00
m2049r
75c550fd19 add copyright notice 2021-03-12 20:02:30 +01:00
m2049r
1b680344b1 call fail() if password cancelled (#726) 2021-03-12 19:57:13 +01:00
m2049r
3329636d32 adjust areContentsTheSame criteria 2021-03-12 19:56:42 +01:00
m2049r
77e9bf7c43 fix Node equality 2021-03-12 19:56:42 +01:00
m2049r
40f5c9365e Merge pull request #725 from m2049r/fix_recyclerdiff
Fix transactions not updating when switching accounts
2021-03-12 01:00:21 +01:00
m2049r
850ba7efef clean whitespace & style 2021-03-12 00:57:22 +01:00
m2049r
548369ca0b fix updating when transfers between accounts 2021-03-12 00:49:12 +01:00
yorha-0x
afc0d5b7bc Update recyclerviews with diffutil (#711)
Remove modified imports

Co-authored-by: m2049r <m2049r@monerujo.io>
2021-03-11 20:57:19 +01:00
24 changed files with 313 additions and 173 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@
/app/alphaStagenet /app/alphaStagenet
/app/prodStagenet /app/prodStagenet
/app/.cxx /app/.cxx
/monerujo.id

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 708 versionCode 710
versionName "1.17.8 'Druk'" versionName "1.17.10 'Druk'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {

View File

@@ -59,7 +59,7 @@ import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.ColorHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.DayNightMode; import com.m2049r.xmrwallet.util.DayNightMode;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.KeyStoreHelper;
@@ -78,7 +78,6 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -376,7 +375,7 @@ public class LoginActivity extends BaseActivity
} }
@Override @Override
public void fail(String walletName1, String password, boolean fingerprintUsed) { public void fail(String walletName) {
} }
}); });
} else { // this cannot really happen as we prefilter choices } else { // this cannot really happen as we prefilter choices
@@ -412,7 +411,7 @@ public class LoginActivity extends BaseActivity
} }
@Override @Override
public void fail(String walletName, String password, boolean fingerprintUsed) { public void fail(String walletName) {
} }
}); });
} else { // this cannot really happen as we prefilter choices } else { // this cannot really happen as we prefilter choices
@@ -707,11 +706,11 @@ public class LoginActivity extends BaseActivity
break; break;
case NetworkType_Testnet: case NetworkType_Testnet:
toolbar.setSubtitle(getString(R.string.connect_testnet)); toolbar.setSubtitle(getString(R.string.connect_testnet));
toolbar.setBackgroundResource(ColorHelper.getThemedResourceId(this, R.attr.colorPrimaryDark)); toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
break; break;
case NetworkType_Stagenet: case NetworkType_Stagenet:
toolbar.setSubtitle(getString(R.string.connect_stagenet)); toolbar.setSubtitle(getString(R.string.connect_stagenet));
toolbar.setBackgroundResource(ColorHelper.getThemedResourceId(this, R.attr.colorPrimaryDark)); toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
break; break;
default: default:
throw new IllegalStateException("NetworkType unknown: " + net); throw new IllegalStateException("NetworkType unknown: " + net);
@@ -1400,7 +1399,7 @@ public class LoginActivity extends BaseActivity
} }
@Override @Override
public void fail(String walletName, String password, boolean fingerprintUsed) { public void fail(String walletName) {
} }
}); });

View File

@@ -258,7 +258,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
walletList.addAll(walletInfos); walletList.addAll(walletInfos);
filterList(); filterList();
adapter.setInfos(displayedList); adapter.setInfos(displayedList);
adapter.notifyDataSetChanged();
// deal with Gunther & FAB animation // deal with Gunther & FAB animation
if (displayedList.isEmpty()) { if (displayedList.isEmpty()) {

View File

@@ -524,7 +524,7 @@ public class NodeFragment extends Fragment
.setNegativeButton(getString(R.string.label_cancel), .setNegativeButton(getString(R.string.label_cancel),
(dialog, id) -> { (dialog, id) -> {
closeDialog(); closeDialog();
nodesAdapter.dataSetChanged(); // to refresh test results nodesAdapter.setNodes(); // to refresh test results
}); });
editDialog = alertDialogBuilder.create(); editDialog = alertDialogBuilder.create();
@@ -588,7 +588,7 @@ public class NodeFragment extends Fragment
if (nodeBackup == null) { if (nodeBackup == null) {
nodesAdapter.addNode(nodeInfo); nodesAdapter.addNode(nodeInfo);
} else { } else {
nodesAdapter.dataSetChanged(); nodesAdapter.setNodes();
} }
} }
} }

View File

@@ -36,10 +36,12 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.android.material.transition.MaterialContainerTransform;
import com.m2049r.xmrwallet.data.UserNotes; import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer; import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@@ -300,7 +302,7 @@ public class TxFragment extends Fragment {
} }
tvTxXmrToKey.setText(key); tvTxXmrToKey.setText(key);
tvDestinationBtc.setText(userNotes.xmrtoDestination); tvDestinationBtc.setText(userNotes.xmrtoDestination);
tvTxAmountBtc.setText(userNotes.xmrtoAmount + " "+ userNotes.xmrtoCurrency); tvTxAmountBtc.setText(userNotes.xmrtoAmount + " " + userNotes.xmrtoCurrency);
switch (userNotes.xmrtoTag) { switch (userNotes.xmrtoTag) {
case "xmrto": case "xmrto":
tvXmrToSupport.setVisibility(View.GONE); tvXmrToSupport.setVisibility(View.GONE);
@@ -329,6 +331,11 @@ public class TxFragment extends Fragment {
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
final MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setDrawingViewId(R.id.fragment_container);
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), R.attr.colorSurface));
setSharedElementEnterTransition(transform);
} }
@Override @Override

View File

@@ -60,7 +60,7 @@ import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.ColorHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@@ -337,7 +337,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
@Override @Override
public void fail(String walletName, String password, boolean fingerprintUsed) { public void fail(String walletName) {
} }
}); });
} }
@@ -424,10 +424,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet); toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
break; break;
case NetworkType_Testnet: case NetworkType_Testnet:
toolbar.setBackgroundResource(ColorHelper.getThemedResourceId(this, R.attr.colorPrimaryDark)); toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
break; break;
case NetworkType_Stagenet: case NetworkType_Stagenet:
toolbar.setBackgroundResource(ColorHelper.getThemedResourceId(this, R.attr.colorPrimaryDark)); toolbar.setBackgroundResource(ThemeHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
break; break;
default: default:
throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType()); throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
@@ -548,10 +548,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
@Override @Override
public void onTxDetailsRequest(TransactionInfo info) { public void onTxDetailsRequest(View view, TransactionInfo info) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putParcelable(TxFragment.ARG_INFO, info); args.putParcelable(TxFragment.ARG_INFO, info);
replaceFragment(new TxFragment(), null, args); replaceFragmentWithTransition(view, new TxFragment(), null, args);
} }
@Override @Override
@@ -828,6 +828,17 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
} }
void replaceFragmentWithTransition(View view, Fragment newFragment, String stackName, Bundle extras) {
if (extras != null) {
newFragment.setArguments(extras);
}
getSupportFragmentManager().beginTransaction()
.addSharedElement(view, getString(R.string.tx_details_transition_name))
.replace(R.id.fragment_container, newFragment)
.addToBackStack(stackName)
.commit();
}
void replaceFragment(Fragment newFragment, String stackName, Bundle extras) { void replaceFragment(Fragment newFragment, String stackName, Bundle extras) {
if (extras != null) { if (extras != null) {
newFragment.setArguments(extras); newFragment.setArguments(extras);
@@ -854,7 +865,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
@Override @Override
public void fail(String walletName, String password, boolean fingerprintUsed) { public void fail(String walletName) {
} }
}); });

View File

@@ -42,6 +42,7 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener; import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener;
import com.google.android.material.transition.MaterialElevationScale;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
@@ -154,7 +155,6 @@ public class WalletFragment extends Fragment
dismissedTransactions.add(adapter.getItem(position).hash); dismissedTransactions.add(adapter.getItem(position).hash);
adapter.removeItem(position); adapter.removeItem(position);
} }
adapter.notifyDataSetChanged();
} }
@Override @Override
@@ -163,7 +163,6 @@ public class WalletFragment extends Fragment
dismissedTransactions.add(adapter.getItem(position).hash); dismissedTransactions.add(adapter.getItem(position).hash);
adapter.removeItem(position); adapter.removeItem(position);
} }
adapter.notifyDataSetChanged();
} }
}); });
@@ -334,7 +333,13 @@ public class WalletFragment extends Fragment
// Callbacks from TransactionInfoAdapter // Callbacks from TransactionInfoAdapter
@Override @Override
public void onInteraction(final View view, final TransactionInfo infoItem) { public void onInteraction(final View view, final TransactionInfo infoItem) {
activityCallback.onTxDetailsRequest(infoItem); final MaterialElevationScale exitTransition = new MaterialElevationScale(false);
exitTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
setExitTransition(exitTransition);
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
setReenterTransition(reenterTransition);
activityCallback.onTxDetailsRequest(view, infoItem);
} }
// called from activity // called from activity
@@ -352,7 +357,6 @@ public class WalletFragment extends Fragment
list.add(info); list.add(info);
} }
adapter.setInfos(list); adapter.setInfos(list);
adapter.notifyDataSetChanged();
} }
updateStatus(wallet); updateStatus(wallet);
} }
@@ -481,7 +485,7 @@ public class WalletFragment extends Fragment
void onSendRequest(); void onSendRequest();
void onTxDetailsRequest(TransactionInfo info); void onTxDetailsRequest(View view, TransactionInfo info);
boolean isSynced(); boolean isSynced();

View File

@@ -64,12 +64,14 @@ public class Node {
return hostAddress.hashCode(); return hostAddress.hashCode();
} }
// Nodes are equal if they are the same host address & are on the same network // Nodes are equal if they are the same host address:port & are on the same network
@Override @Override
public boolean equals(Object other) { public boolean equals(Object other) {
if (!(other instanceof Node)) return false; if (!(other instanceof Node)) return false;
final Node anotherNode = (Node) other; final Node anotherNode = (Node) other;
return (hostAddress.equals(anotherNode.hostAddress) && (networkType == anotherNode.networkType)); return (hostAddress.equals(anotherNode.hostAddress)
&& (rpcPort == anotherNode.rpcPort)
&& (networkType == anotherNode.networkType));
} }
static public Node fromString(String nodeString) { static public Node fromString(String nodeString) {

View File

@@ -227,8 +227,7 @@ public class NodeInfo extends Node {
if (response.isSuccessful()) { if (response.isSuccessful()) {
ResponseBody respBody = response.body(); // closed through Response object ResponseBody respBody = response.body(); // closed through Response object
if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
final JSONObject json = new JSONObject( final JSONObject json = new JSONObject(respBody.string());
respBody.string());
String rpcVersion = json.getString("jsonrpc"); String rpcVersion = json.getString("jsonrpc");
if (!RPC_VERSION.equals(rpcVersion)) if (!RPC_VERSION.equals(rpcVersion))
return false; return false;

View File

@@ -332,7 +332,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
send(); send();
} }
public void fail(String walletName, String password, boolean fingerprintUsed) { public void fail(String walletName) {
getActivity().runOnUiThread(() -> { getActivity().runOnUiThread(() -> {
bSend.setEnabled(sendCountdown > 0); // allow to try again bSend.setEnabled(sendCountdown > 0); // allow to try again
}); });

View File

@@ -227,7 +227,7 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
send(); send();
} }
public void fail(String walletName, String password, boolean fingerprintUsed) { public void fail(String walletName) {
getActivity().runOnUiThread(() -> { getActivity().runOnUiThread(() -> {
bSend.setEnabled(true); // allow to try again bSend.setEnabled(true); // allow to try again
}); });

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2021 yorha-0x
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.layout;
import androidx.recyclerview.widget.DiffUtil;
import java.util.List;
public abstract class DiffCallback<T> extends DiffUtil.Callback {
protected final List<T> mOldList;
protected final List<T> mNewList;
public DiffCallback(List<T> oldList, List<T> newList) {
this.mOldList = oldList;
this.mNewList = newList;
}
@Override
public int getOldListSize() {
return mOldList.size();
}
@Override
public int getNewListSize() {
return mNewList.size();
}
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
}

View File

@@ -25,11 +25,12 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.util.ColorHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@@ -53,7 +54,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
private final List<NodeInfo> nodeItems = new ArrayList<>(); private final List<NodeInfo> nodeItems = new ArrayList<>();
private final OnInteractionListener listener; private final OnInteractionListener listener;
private Context context; private final Context context;
public NodeInfoAdapter(Context context, OnInteractionListener listener) { public NodeInfoAdapter(Context context, OnInteractionListener listener) {
this.context = context; this.context = context;
@@ -63,11 +64,34 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
TS_FORMATTER.setTimeZone(tz); TS_FORMATTER.setTimeZone(tz);
} }
private static class NodeDiff extends DiffCallback<NodeInfo> {
public NodeDiff(List<NodeInfo> oldList, List<NodeInfo> newList) {
super(oldList, newList);
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
final NodeInfo oldItem = mOldList.get(oldItemPosition);
final NodeInfo newItem = mNewList.get(newItemPosition);
return (oldItem.getTimestamp() == newItem.getTimestamp())
&& (oldItem.isTested() == newItem.isTested())
&& (oldItem.isValid() == newItem.isValid())
&& (oldItem.getResponseTime() == newItem.getResponseTime())
&& (oldItem.isSelected() == newItem.isSelected())
&& (oldItem.getName().equals(newItem.getName()));
}
}
@Override @Override
public @NonNull public @NonNull
ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_node, parent, false);
.inflate(R.layout.item_node, parent, false);
return new ViewHolder(view); return new ViewHolder(view);
} }
@@ -82,25 +106,29 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
} }
public void addNode(NodeInfo node) { public void addNode(NodeInfo node) {
List<NodeInfo> newItems = new ArrayList<>(nodeItems);
if (!nodeItems.contains(node)) if (!nodeItems.contains(node))
nodeItems.add(node); newItems.add(node);
dataSetChanged(); // in case the nodeinfo has changed setNodes(newItems); // in case the nodeinfo has changed
} }
public void dataSetChanged() { public void setNodes(Collection<NodeInfo> newItemsCollection) {
Collections.sort(nodeItems, NodeInfo.BestNodeComparator); List<NodeInfo> newItems;
notifyDataSetChanged(); if (newItemsCollection != null) {
newItems = new ArrayList<>(newItemsCollection);
Collections.sort(newItems, NodeInfo.BestNodeComparator);
} else {
newItems = new ArrayList<>();
} }
final NodeInfoAdapter.NodeDiff diffCallback = new NodeInfoAdapter.NodeDiff(nodeItems, newItems);
public void setNodes(Collection<NodeInfo> data) { final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
nodeItems.clear(); nodeItems.clear();
if (data != null) { nodeItems.addAll(newItems);
for (NodeInfo node : data) { diffResult.dispatchUpdatesTo(this);
if (!nodeItems.contains(node))
nodeItems.add(node);
} }
}
dataSetChanged(); public void setNodes() {
setNodes(nodeItems);
} }
private boolean itemsClickable = true; private boolean itemsClickable = true;
@@ -130,7 +158,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
showStar(); showStar();
if (!nodeItem.isFavourite()) { if (!nodeItem.isFavourite()) {
nodeItem.setSelected(false); nodeItem.setSelected(false);
notifyDataSetChanged(); setNodes(nodeItems);
} }
}); });
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
@@ -154,7 +182,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
Helper.showTimeDifference(tvIp, nodeItem.getTimestamp()); Helper.showTimeDifference(tvIp, nodeItem.getTimestamp());
} else { } else {
tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode())); tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode()));
tvIp.setTextColor(ColorHelper.getThemedColor(tvIp.getContext(), R.attr.colorError)); tvIp.setTextColor(ThemeHelper.getThemedColor(context, R.attr.colorError));
} }
} else { } else {
tvIp.setText(context.getResources().getString(R.string.node_testing, nodeItem.getHostAddress())); tvIp.setText(context.getResources().getString(R.string.node_testing, nodeItem.getHostAddress()));

View File

@@ -23,7 +23,9 @@ import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
@@ -38,6 +40,7 @@ import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.TimeZone; import java.util.TimeZone;
import timber.log.Timber; import timber.log.Timber;
@@ -46,10 +49,10 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm"); private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
private int outboundColour; private final int outboundColour;
private int inboundColour; private final int inboundColour;
private int pendingColour; private final int pendingColour;
private int failedColour; private final int failedColour;
public interface OnInteractionListener { public interface OnInteractionListener {
void onInteraction(View view, TransactionInfo item); void onInteraction(View view, TransactionInfo item);
@@ -58,7 +61,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
private final List<TransactionInfo> infoItems; private final List<TransactionInfo> infoItems;
private final OnInteractionListener listener; private final OnInteractionListener listener;
private Context context; private final Context context;
public TransactionInfoAdapter(Context context, OnInteractionListener listener) { public TransactionInfoAdapter(Context context, OnInteractionListener listener) {
this.context = context; this.context = context;
@@ -73,10 +76,32 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
DATETIME_FORMATTER.setTimeZone(tz); DATETIME_FORMATTER.setTimeZone(tz);
} }
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {
public TransactionInfoDiff(List<TransactionInfo> oldList, List<TransactionInfo> newList) {
super(oldList, newList);
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).hash.equals(mNewList.get(newItemPosition).hash);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
final TransactionInfo oldItem = mOldList.get(oldItemPosition);
final TransactionInfo newItem = mNewList.get(newItemPosition);
return (oldItem.direction == newItem.direction)
&& (oldItem.isPending == newItem.isPending)
&& (oldItem.isFailed == newItem.isFailed)
&& (Objects.equals(oldItem.notes, newItem.notes));
}
}
@NonNull
@Override @Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transaction, parent, false);
.inflate(R.layout.item_transaction, parent, false);
return new ViewHolder(view); return new ViewHolder(view);
} }
@@ -90,23 +115,26 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
return infoItems.size(); return infoItems.size();
} }
public void setInfos(List<TransactionInfo> data) { public void setInfos(List<TransactionInfo> newItems) {
// TODO do stuff with data so we can really recycle elements (i.e. add only new tx) if (newItems == null) {
// as the TransactionInfo items are always recreated, we cannot recycle newItems = new ArrayList<>();
infoItems.clear();
if (data != null) {
Timber.d("setInfos %s", data.size());
infoItems.addAll(data);
Collections.sort(infoItems);
} else {
Timber.d("setInfos null"); Timber.d("setInfos null");
} else {
Timber.d("setInfos %s", newItems.size());
} }
notifyDataSetChanged(); Collections.sort(newItems);
final DiffCallback<TransactionInfo> diffCallback = new TransactionInfoAdapter.TransactionInfoDiff(infoItems, newItems);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
infoItems.clear();
infoItems.addAll(newItems);
diffResult.dispatchUpdatesTo(this);
} }
public void removeItem(int position) { public void removeItem(int position) {
infoItems.remove(position); List<TransactionInfo> newItems = new ArrayList<>(infoItems);
notifyItemRemoved(position); if (newItems.size() > position)
newItems.remove(position);
setInfos(newItems); // in case the nodeinfo has changed
} }
public TransactionInfo getItem(int position) { public TransactionInfo getItem(int position) {
@@ -139,7 +167,8 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
} }
void bind(int position) { void bind(int position) {
this.infoItem = infoItems.get(position); infoItem = infoItems.get(position);
itemView.setTransitionName(context.getString(R.string.tx_item_transition_name, infoItem.hash));
UserNotes userNotes = new UserNotes(infoItem.notes); UserNotes userNotes = new UserNotes(infoItem.notes);
if (userNotes.xmrtoKey != null) { if (userNotes.xmrtoKey != null) {

View File

@@ -17,8 +17,6 @@
package com.m2049r.xmrwallet.layout; package com.m2049r.xmrwallet.layout;
import android.content.Context; import android.content.Context;
import androidx.appcompat.widget.PopupMenu;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@@ -26,6 +24,11 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.PopupMenu;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
@@ -63,6 +66,24 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
DATETIME_FORMATTER.setTimeZone(tz); DATETIME_FORMATTER.setTimeZone(tz);
} }
private static class WalletInfoDiff extends DiffCallback<WalletManager.WalletInfo> {
public WalletInfoDiff(List<WalletManager.WalletInfo> oldList, List<WalletManager.WalletInfo> newList) {
super(oldList, newList);
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).name.equals(mNewList.get(newItemPosition).name);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).compareTo(mNewList.get(newItemPosition)) == 0;
}
}
@NonNull
@Override @Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(parent.getContext())
@@ -84,18 +105,19 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
return infoItems.get(position); return infoItems.get(position);
} }
public void setInfos(List<WalletManager.WalletInfo> data) { public void setInfos(List<WalletManager.WalletInfo> newItems) {
// TODO do stuff with data so we can really recycle elements (i.e. add only new tx) if (newItems == null) {
// as the WalletInfo items are always recreated, we cannot recycle newItems = new ArrayList<>();
infoItems.clear();
if (data != null) {
Timber.d("setInfos %s", data.size());
infoItems.addAll(data);
Collections.sort(infoItems);
} else {
Timber.d("setInfos null"); Timber.d("setInfos null");
} else {
Timber.d("setInfos %s", newItems.size());
} }
notifyDataSetChanged(); Collections.sort(newItems);
final DiffCallback<WalletManager.WalletInfo> diffCallback = new WalletInfoAdapter.WalletInfoDiff(infoItems, newItems);
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
infoItems.clear();
infoItems.addAll(newItems);
diffResult.dispatchUpdatesTo(this);
} }
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
@@ -110,9 +132,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
tvName = itemView.findViewById(R.id.tvName); tvName = itemView.findViewById(R.id.tvName);
tvAddress = itemView.findViewById(R.id.tvAddress); tvAddress = itemView.findViewById(R.id.tvAddress);
ibOptions = itemView.findViewById(R.id.ibOptions); ibOptions = itemView.findViewById(R.id.ibOptions);
ibOptions.setOnClickListener(new View.OnClickListener() { ibOptions.setOnClickListener(view -> {
@Override
public void onClick(View view) {
if (popupOpen) return; if (popupOpen) return;
//creating a popup menu //creating a popup menu
PopupMenu popup = new PopupMenu(context, ibOptions); PopupMenu popup = new PopupMenu(context, ibOptions);
@@ -120,25 +140,16 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
popup.inflate(R.menu.list_context_menu); popup.inflate(R.menu.list_context_menu);
popupOpen = true; popupOpen = true;
//adding click listener //adding click listener
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { popup.setOnMenuItemClickListener(item -> {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (listener != null) { if (listener != null) {
return listener.onContextInteraction(item, infoItem); return listener.onContextInteraction(item, infoItem);
} }
return false; return false;
}
}); });
//displaying the popup //displaying the popup
popup.show(); popup.show();
popup.setOnDismissListener(new PopupMenu.OnDismissListener() { popup.setOnDismissListener(menu -> popupOpen = false);
@Override
public void onDismiss(PopupMenu menu) {
popupOpen = false;
}
});
}
}); });
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
} }

View File

@@ -23,7 +23,6 @@ import android.content.ClipData;
import android.content.ClipDescription; import android.content.ClipDescription;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
@@ -484,7 +483,6 @@ public class Helper {
} }
etPassword.getEditText().addTextChangedListener(new TextWatcher() { etPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
if (etPassword.getError() != null) { if (etPassword.getError() != null) {
@@ -508,8 +506,8 @@ public class Helper {
.setCancelable(false) .setCancelable(false)
.setPositiveButton(context.getString(R.string.label_ok), null) .setPositiveButton(context.getString(R.string.label_ok), null)
.setNegativeButton(context.getString(R.string.label_cancel), .setNegativeButton(context.getString(R.string.label_cancel),
new DialogInterface.OnClickListener() { (dialog, id) -> {
public void onClick(DialogInterface dialog, int id) { action.fail(wallet);
Helper.hideKeyboardAlways((Activity) context); Helper.hideKeyboardAlways((Activity) context);
cancelSignal.cancel(); cancelSignal.cancel();
if (passwordTask != null) { if (passwordTask != null) {
@@ -518,7 +516,6 @@ public class Helper {
} }
dialog.cancel(); dialog.cancel();
openDialog = null; openDialog = null;
}
}); });
openDialog = alertDialogBuilder.create(); openDialog = alertDialogBuilder.create();
@@ -561,9 +558,7 @@ public class Helper {
}; };
} }
openDialog.setOnShowListener(new DialogInterface.OnShowListener() { openDialog.setOnShowListener(dialog -> {
@Override
public void onShow(DialogInterface dialog) {
if (fingerprintAuthAllowed && fingerprintAuthCallback != null) { if (fingerprintAuthAllowed && fingerprintAuthCallback != null) {
tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icFingerprint, null, null, null); tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icFingerprint, null, null, null);
tvOpenPrompt.setText(context.getText(R.string.prompt_fingerprint_auth)); tvOpenPrompt.setText(context.getText(R.string.prompt_fingerprint_auth));
@@ -573,22 +568,17 @@ public class Helper {
etPassword.requestFocus(); etPassword.requestFocus();
} }
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() { button.setOnClickListener(view -> {
@Override
public void onClick(View view) {
String pass = etPassword.getEditText().getText().toString(); String pass = etPassword.getEditText().getText().toString();
if (passwordTask == null) { if (passwordTask == null) {
passwordTask = new PasswordTask(pass, false); passwordTask = new PasswordTask(pass, false);
passwordTask.execute(); passwordTask.execute();
} }
}
}); });
}
}); });
// accept keyboard "ok" // accept keyboard "ok"
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) { || (actionId == EditorInfo.IME_ACTION_DONE)) {
String pass = etPassword.getEditText().getText().toString(); String pass = etPassword.getEditText().getText().toString();
@@ -599,7 +589,6 @@ public class Helper {
return true; return true;
} }
return false; return false;
}
}); });
if (Helper.preventScreenshot()) { if (Helper.preventScreenshot()) {
@@ -613,7 +602,7 @@ public class Helper {
public interface PasswordAction { public interface PasswordAction {
void act(String walletName, String password, boolean fingerprintUsed); void act(String walletName, String password, boolean fingerprintUsed);
void fail(String walletName, String password, boolean fingerprintUsed); void fail(String walletName);
} }
static private boolean processPasswordEntry(Context context, String walletName, String pass, boolean fingerprintUsed, PasswordAction action) { static private boolean processPasswordEntry(Context context, String walletName, String pass, boolean fingerprintUsed, PasswordAction action) {
@@ -622,7 +611,7 @@ public class Helper {
action.act(walletName, walletPassword, fingerprintUsed); action.act(walletName, walletPassword, fingerprintUsed);
return true; return true;
} else { } else {
action.fail(walletName, walletPassword, fingerprintUsed); action.fail(walletName);
return false; return false;
} }
} }
@@ -667,8 +656,8 @@ public class Helper {
} }
view.setText(msg); view.setText(msg);
if (hours >= STALE_NODE_HOURS) if (hours >= STALE_NODE_HOURS)
view.setTextColor(ColorHelper.getThemedColor(view.getContext(), R.attr.colorError)); view.setTextColor(ThemeHelper.getThemedColor(view.getContext(), R.attr.colorError));
else else
view.setTextColor(ColorHelper.getThemedColor(view.getContext(), android.R.attr.textColorPrimary)); view.setTextColor(ThemeHelper.getThemedColor(view.getContext(), android.R.attr.textColorPrimary));
} }
} }

View File

@@ -17,19 +17,26 @@
package com.m2049r.xmrwallet.util; package com.m2049r.xmrwallet.util;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color; import android.graphics.Color;
import android.util.TypedValue;
import com.m2049r.xmrwallet.R; import androidx.annotation.ColorInt;
public class ColorHelper { public class ThemeHelper {
static public int getThemedResourceId(Context ctx, int attrId) { static public int getThemedResourceId(Context ctx, int attrId) {
TypedArray styledAttributes = ctx.getTheme().obtainStyledAttributes(R.style.MyMaterialTheme, new int[]{attrId}); final TypedValue typedValue = new TypedValue();
return styledAttributes.getResourceId(0, 0); if (ctx.getTheme().resolveAttribute(attrId, typedValue, true))
return typedValue.resourceId;
else
return 0;
} }
@ColorInt
static public int getThemedColor(Context ctx, int attrId) { static public int getThemedColor(Context ctx, int attrId) {
TypedArray styledAttributes = ctx.getTheme().obtainStyledAttributes(R.style.MyMaterialTheme, new int[]{attrId}); final TypedValue typedValue = new TypedValue();
return styledAttributes.getColor(0, Color.BLACK); if (ctx.getTheme().resolveAttribute(attrId, typedValue, true))
return typedValue.data;
else
return Color.BLACK;
} }
} }

View File

@@ -42,7 +42,7 @@ import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi; import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.ColorHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ServiceHelper; import com.m2049r.xmrwallet.util.ServiceHelper;
@@ -180,7 +180,7 @@ public class ExchangeView extends LinearLayout {
// make progress circle gray // make progress circle gray
pbExchange.getIndeterminateDrawable(). pbExchange.getIndeterminateDrawable().
setColorFilter(ColorHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant), setColorFilter(ThemeHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant),
android.graphics.PorterDuff.Mode.MULTIPLY); android.graphics.PorterDuff.Mode.MULTIPLY);
sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { sCurrencyA.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

View File

@@ -4,7 +4,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_margin="8dp"
android:fillViewport="true"> android:fillViewport="true"
android:transitionName="@string/tx_details_transition_name">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -141,6 +141,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="80dp" android:paddingBottom="80dp"
android:transitionGroup="true"
app:layoutManager="LinearLayoutManager" app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_transaction" /> tools:listitem="@layout/item_transaction" />

View File

@@ -1,2 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources></resources> <resources>
<integer name="tx_item_transition_duration">300</integer>
</resources>

View File

@@ -471,4 +471,8 @@
<item>Ethereum</item> <item>Ethereum</item>
<item>Litecoin</item> <item>Litecoin</item>
</string-array> </string-array>
<string name="tx_item_transition_name" translatable="false">tx_transition_%1$s</string>
<string name="tx_details_transition_name" translatable="false">tx_transition</string>
</resources> </resources>

View File

@@ -36,7 +36,7 @@
</style> </style>
<style name="AppCard" parent="Widget.MaterialComponents.CardView"> <style name="AppCard" parent="Widget.MaterialComponents.CardView">
<item name="cardElevation">8dp</item> <item name="cardElevation">4dp</item>
<item name="cardCornerRadius">1dp</item> <item name="cardCornerRadius">1dp</item>
</style> </style>