mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 00:53:36 +02:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c5a035437b | ||
![]() |
75c550fd19 | ||
![]() |
1b680344b1 | ||
![]() |
3329636d32 | ||
![]() |
77e9bf7c43 | ||
![]() |
40f5c9365e | ||
![]() |
850ba7efef | ||
![]() |
548369ca0b | ||
![]() |
afc0d5b7bc | ||
![]() |
02e92db59a | ||
![]() |
2b34454214 | ||
![]() |
12706119d3 | ||
![]() |
f7a762f32b | ||
![]() |
86d5d2a2cb |
@@ -2,13 +2,13 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '29.0.2'
|
||||
buildToolsVersion '29.0.3'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 707
|
||||
versionName "1.17.7 'Druk'"
|
||||
versionCode 709
|
||||
versionName "1.17.9 'Druk'"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
|
@@ -50,9 +50,8 @@ import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
import com.m2049r.xmrwallet.util.ledger.Monero;
|
||||
import com.m2049r.xmrwallet.widget.PasswordEntryView;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
import com.nulabinc.zxcvbn.Strength;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
@@ -70,7 +69,7 @@ public class GenerateFragment extends Fragment {
|
||||
static final String TYPE_VIEWONLY = "view";
|
||||
|
||||
private TextInputLayout etWalletName;
|
||||
private TextInputLayout etWalletPassword;
|
||||
private PasswordEntryView etWalletPassword;
|
||||
private LinearLayout llFingerprintAuth;
|
||||
private TextInputLayout etWalletAddress;
|
||||
private TextInputLayout etWalletMnemonic;
|
||||
@@ -131,21 +130,6 @@ public class GenerateFragment extends Fragment {
|
||||
});
|
||||
clearErrorOnTextEntry(etWalletName);
|
||||
|
||||
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
checkPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||
@Override
|
||||
public void onFocusChange(View v, boolean hasFocus) {
|
||||
@@ -354,45 +338,10 @@ public class GenerateFragment extends Fragment {
|
||||
});
|
||||
|
||||
etWalletName.requestFocus();
|
||||
initZxcvbn();
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
Zxcvbn zxcvbn = new Zxcvbn();
|
||||
|
||||
// initialize zxcvbn engine in background thread
|
||||
private void initZxcvbn() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
zxcvbn.measure("");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void checkPassword() {
|
||||
String password = etWalletPassword.getEditText().getText().toString();
|
||||
if (!password.isEmpty()) {
|
||||
Strength strength = zxcvbn.measure(password);
|
||||
int msg;
|
||||
double guessesLog10 = strength.getGuessesLog10();
|
||||
if (guessesLog10 < 10)
|
||||
msg = R.string.password_weak;
|
||||
else if (guessesLog10 < 11)
|
||||
msg = R.string.password_fair;
|
||||
else if (guessesLog10 < 12)
|
||||
msg = R.string.password_good;
|
||||
else if (guessesLog10 < 13)
|
||||
msg = R.string.password_strong;
|
||||
else
|
||||
msg = R.string.password_very_strong;
|
||||
etWalletPassword.setError(getResources().getString(msg));
|
||||
} else {
|
||||
etWalletPassword.setError(null);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkName() {
|
||||
String name = etWalletName.getEditText().getText().toString();
|
||||
boolean ok = true;
|
||||
@@ -429,7 +378,7 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
|
||||
private long getHeight() {
|
||||
long height = 0;
|
||||
long height = -1;
|
||||
|
||||
String restoreHeight = etWalletRestoreHeight.getEditText().getText().toString().trim();
|
||||
if (restoreHeight.isEmpty()) return -1;
|
||||
@@ -440,7 +389,7 @@ public class GenerateFragment extends Fragment {
|
||||
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||
} catch (ParseException ex) {
|
||||
}
|
||||
if ((height <= 0) && (restoreHeight.length() == 8))
|
||||
if ((height < 0) && (restoreHeight.length() == 8))
|
||||
try {
|
||||
// is it a date without dashes?
|
||||
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
|
||||
@@ -448,7 +397,7 @@ public class GenerateFragment extends Fragment {
|
||||
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||
} catch (ParseException ex) {
|
||||
}
|
||||
if (height <= 0)
|
||||
if (height < 0)
|
||||
try {
|
||||
// or is it a height?
|
||||
height = Long.parseLong(restoreHeight);
|
||||
@@ -654,24 +603,7 @@ public class GenerateFragment extends Fragment {
|
||||
final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed);
|
||||
final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase);
|
||||
|
||||
etSeed.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (etSeed.getError() != null) {
|
||||
etSeed.setError(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start,
|
||||
int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start,
|
||||
int before, int count) {
|
||||
}
|
||||
});
|
||||
clearErrorOnTextEntry(etSeed);
|
||||
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
|
@@ -56,6 +56,7 @@ import com.m2049r.xmrwallet.util.FingerprintHelper;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||
import com.m2049r.xmrwallet.widget.PasswordEntryView;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
@@ -473,7 +474,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(getActivity());
|
||||
alertDialogBuilder.setView(promptsView);
|
||||
|
||||
final TextInputLayout etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA);
|
||||
final PasswordEntryView etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA);
|
||||
etPasswordA.setHint(getString(R.string.prompt_changepw, walletName));
|
||||
|
||||
final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB);
|
||||
@@ -509,9 +510,6 @@ public class GenerateReviewFragment extends Fragment {
|
||||
etPasswordA.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (etPasswordA.getError() != null) {
|
||||
etPasswordA.setError(null);
|
||||
}
|
||||
if (etPasswordB.getError() != null) {
|
||||
etPasswordB.setError(null);
|
||||
}
|
||||
@@ -531,9 +529,6 @@ public class GenerateReviewFragment extends Fragment {
|
||||
etPasswordB.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (etPasswordA.getError() != null) {
|
||||
etPasswordA.setError(null);
|
||||
}
|
||||
if (etPasswordB.getError() != null) {
|
||||
etPasswordB.setError(null);
|
||||
}
|
||||
@@ -564,29 +559,20 @@ public class GenerateReviewFragment extends Fragment {
|
||||
});
|
||||
|
||||
openDialog = alertDialogBuilder.create();
|
||||
openDialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
@Override
|
||||
public void onShow(final DialogInterface dialog) {
|
||||
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
||||
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
||||
// disallow empty passwords
|
||||
if (newPasswordA.isEmpty()) {
|
||||
etPasswordA.setError(getString(R.string.generate_empty_passwordB));
|
||||
} else if (!newPasswordA.equals(newPasswordB)) {
|
||||
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
||||
} else {
|
||||
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||
Helper.hideKeyboardAlways(getActivity());
|
||||
openDialog.dismiss();
|
||||
openDialog = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
openDialog.setOnShowListener(dialog -> {
|
||||
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(view -> {
|
||||
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
||||
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
||||
if (!newPasswordA.equals(newPasswordB)) {
|
||||
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
||||
} else {
|
||||
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||
Helper.hideKeyboardAlways(getActivity());
|
||||
openDialog.dismiss();
|
||||
openDialog = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// accept keyboard "ok"
|
||||
@@ -597,9 +583,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
||||
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
||||
// disallow empty passwords
|
||||
if (newPasswordA.isEmpty()) {
|
||||
etPasswordA.setError(getString(R.string.generate_empty_passwordB));
|
||||
} else if (!newPasswordA.equals(newPasswordB)) {
|
||||
if (!newPasswordA.equals(newPasswordB)) {
|
||||
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
||||
} else {
|
||||
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||
|
@@ -376,7 +376,7 @@ public class LoginActivity extends BaseActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName1, String password, boolean fingerprintUsed) {
|
||||
public void fail(String walletName) {
|
||||
}
|
||||
});
|
||||
} else { // this cannot really happen as we prefilter choices
|
||||
@@ -412,7 +412,7 @@ public class LoginActivity extends BaseActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void fail(String walletName) {
|
||||
}
|
||||
});
|
||||
} else { // this cannot really happen as we prefilter choices
|
||||
@@ -1400,7 +1400,7 @@ public class LoginActivity extends BaseActivity
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void fail(String walletName) {
|
||||
}
|
||||
|
||||
});
|
||||
|
@@ -258,7 +258,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
walletList.addAll(walletInfos);
|
||||
filterList();
|
||||
adapter.setInfos(displayedList);
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
// deal with Gunther & FAB animation
|
||||
if (displayedList.isEmpty()) {
|
||||
|
@@ -524,7 +524,7 @@ public class NodeFragment extends Fragment
|
||||
.setNegativeButton(getString(R.string.label_cancel),
|
||||
(dialog, id) -> {
|
||||
closeDialog();
|
||||
nodesAdapter.dataSetChanged(); // to refresh test results
|
||||
nodesAdapter.setNodes(); // to refresh test results
|
||||
});
|
||||
|
||||
editDialog = alertDialogBuilder.create();
|
||||
@@ -588,7 +588,7 @@ public class NodeFragment extends Fragment
|
||||
if (nodeBackup == null) {
|
||||
nodesAdapter.addNode(nodeInfo);
|
||||
} else {
|
||||
nodesAdapter.dataSetChanged();
|
||||
nodesAdapter.setNodes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -337,7 +337,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void fail(String walletName) {
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -854,7 +854,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void fail(String walletName) {
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -154,7 +154,6 @@ public class WalletFragment extends Fragment
|
||||
dismissedTransactions.add(adapter.getItem(position).hash);
|
||||
adapter.removeItem(position);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -163,7 +162,6 @@ public class WalletFragment extends Fragment
|
||||
dismissedTransactions.add(adapter.getItem(position).hash);
|
||||
adapter.removeItem(position);
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -352,7 +350,6 @@ public class WalletFragment extends Fragment
|
||||
list.add(info);
|
||||
}
|
||||
adapter.setInfos(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
updateStatus(wallet);
|
||||
}
|
||||
|
@@ -64,12 +64,14 @@ public class Node {
|
||||
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
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof Node)) return false;
|
||||
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) {
|
||||
|
@@ -227,8 +227,7 @@ public class NodeInfo extends Node {
|
||||
if (response.isSuccessful()) {
|
||||
ResponseBody respBody = response.body(); // closed through Response object
|
||||
if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
|
||||
final JSONObject json = new JSONObject(
|
||||
respBody.string());
|
||||
final JSONObject json = new JSONObject(respBody.string());
|
||||
String rpcVersion = json.getString("jsonrpc");
|
||||
if (!RPC_VERSION.equals(rpcVersion))
|
||||
return false;
|
||||
|
@@ -332,7 +332,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
send();
|
||||
}
|
||||
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void fail(String walletName) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
bSend.setEnabled(sendCountdown > 0); // allow to try again
|
||||
});
|
||||
|
@@ -227,7 +227,7 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
|
||||
send();
|
||||
}
|
||||
|
||||
public void fail(String walletName, String password, boolean fingerprintUsed) {
|
||||
public void fail(String walletName) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
bSend.setEnabled(true); // allow to try again
|
||||
});
|
||||
|
@@ -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);
|
||||
}
|
@@ -25,6 +25,7 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
@@ -53,7 +54,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
||||
private final List<NodeInfo> nodeItems = new ArrayList<>();
|
||||
private final OnInteractionListener listener;
|
||||
|
||||
private Context context;
|
||||
private final Context context;
|
||||
|
||||
public NodeInfoAdapter(Context context, OnInteractionListener listener) {
|
||||
this.context = context;
|
||||
@@ -63,11 +64,34 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
||||
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
|
||||
public @NonNull
|
||||
ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_node, parent, false);
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_node, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@@ -82,25 +106,29 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
||||
}
|
||||
|
||||
public void addNode(NodeInfo node) {
|
||||
List<NodeInfo> newItems = new ArrayList<>(nodeItems);
|
||||
if (!nodeItems.contains(node))
|
||||
nodeItems.add(node);
|
||||
dataSetChanged(); // in case the nodeinfo has changed
|
||||
newItems.add(node);
|
||||
setNodes(newItems); // in case the nodeinfo has changed
|
||||
}
|
||||
|
||||
public void dataSetChanged() {
|
||||
Collections.sort(nodeItems, NodeInfo.BestNodeComparator);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setNodes(Collection<NodeInfo> data) {
|
||||
nodeItems.clear();
|
||||
if (data != null) {
|
||||
for (NodeInfo node : data) {
|
||||
if (!nodeItems.contains(node))
|
||||
nodeItems.add(node);
|
||||
}
|
||||
public void setNodes(Collection<NodeInfo> newItemsCollection) {
|
||||
List<NodeInfo> newItems;
|
||||
if (newItemsCollection != null) {
|
||||
newItems = new ArrayList<>(newItemsCollection);
|
||||
Collections.sort(newItems, NodeInfo.BestNodeComparator);
|
||||
} else {
|
||||
newItems = new ArrayList<>();
|
||||
}
|
||||
dataSetChanged();
|
||||
final NodeInfoAdapter.NodeDiff diffCallback = new NodeInfoAdapter.NodeDiff(nodeItems, newItems);
|
||||
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
|
||||
nodeItems.clear();
|
||||
nodeItems.addAll(newItems);
|
||||
diffResult.dispatchUpdatesTo(this);
|
||||
}
|
||||
|
||||
public void setNodes() {
|
||||
setNodes(nodeItems);
|
||||
}
|
||||
|
||||
private boolean itemsClickable = true;
|
||||
@@ -130,7 +158,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
||||
showStar();
|
||||
if (!nodeItem.isFavourite()) {
|
||||
nodeItem.setSelected(false);
|
||||
notifyDataSetChanged();
|
||||
setNodes(nodeItems);
|
||||
}
|
||||
});
|
||||
itemView.setOnClickListener(this);
|
||||
|
@@ -23,7 +23,9 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
@@ -46,10 +48,10 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
|
||||
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||
|
||||
private int outboundColour;
|
||||
private int inboundColour;
|
||||
private int pendingColour;
|
||||
private int failedColour;
|
||||
private final int outboundColour;
|
||||
private final int inboundColour;
|
||||
private final int pendingColour;
|
||||
private final int failedColour;
|
||||
|
||||
public interface OnInteractionListener {
|
||||
void onInteraction(View view, TransactionInfo item);
|
||||
@@ -58,7 +60,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
private final List<TransactionInfo> infoItems;
|
||||
private final OnInteractionListener listener;
|
||||
|
||||
private Context context;
|
||||
private final Context context;
|
||||
|
||||
public TransactionInfoAdapter(Context context, OnInteractionListener listener) {
|
||||
this.context = context;
|
||||
@@ -73,10 +75,31 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_transaction, parent, false);
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transaction, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@@ -90,23 +113,26 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
return infoItems.size();
|
||||
}
|
||||
|
||||
public void setInfos(List<TransactionInfo> data) {
|
||||
// TODO do stuff with data so we can really recycle elements (i.e. add only new tx)
|
||||
// as the TransactionInfo items are always recreated, we cannot recycle
|
||||
infoItems.clear();
|
||||
if (data != null) {
|
||||
Timber.d("setInfos %s", data.size());
|
||||
infoItems.addAll(data);
|
||||
Collections.sort(infoItems);
|
||||
} else {
|
||||
public void setInfos(List<TransactionInfo> newItems) {
|
||||
if (newItems == null) {
|
||||
newItems = new ArrayList<>();
|
||||
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) {
|
||||
infoItems.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
List<TransactionInfo> newItems = new ArrayList<>(infoItems);
|
||||
if (newItems.size() > position)
|
||||
newItems.remove(position);
|
||||
setInfos(newItems); // in case the nodeinfo has changed
|
||||
}
|
||||
|
||||
public TransactionInfo getItem(int position) {
|
||||
|
@@ -17,8 +17,6 @@
|
||||
package com.m2049r.xmrwallet.layout;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -26,6 +24,11 @@ import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
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.model.WalletManager;
|
||||
|
||||
@@ -63,6 +66,24 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
||||
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
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
@@ -84,18 +105,19 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
||||
return infoItems.get(position);
|
||||
}
|
||||
|
||||
public void setInfos(List<WalletManager.WalletInfo> data) {
|
||||
// TODO do stuff with data so we can really recycle elements (i.e. add only new tx)
|
||||
// as the WalletInfo items are always recreated, we cannot recycle
|
||||
infoItems.clear();
|
||||
if (data != null) {
|
||||
Timber.d("setInfos %s", data.size());
|
||||
infoItems.addAll(data);
|
||||
Collections.sort(infoItems);
|
||||
} else {
|
||||
public void setInfos(List<WalletManager.WalletInfo> newItems) {
|
||||
if (newItems == null) {
|
||||
newItems = new ArrayList<>();
|
||||
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 {
|
||||
@@ -110,35 +132,24 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
tvAddress = itemView.findViewById(R.id.tvAddress);
|
||||
ibOptions = itemView.findViewById(R.id.ibOptions);
|
||||
ibOptions.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (popupOpen) return;
|
||||
//creating a popup menu
|
||||
PopupMenu popup = new PopupMenu(context, ibOptions);
|
||||
//inflating menu from xml resource
|
||||
popup.inflate(R.menu.list_context_menu);
|
||||
popupOpen = true;
|
||||
//adding click listener
|
||||
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
if (listener != null) {
|
||||
return listener.onContextInteraction(item, infoItem);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
//displaying the popup
|
||||
popup.show();
|
||||
popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(PopupMenu menu) {
|
||||
popupOpen = false;
|
||||
}
|
||||
});
|
||||
ibOptions.setOnClickListener(view -> {
|
||||
if (popupOpen) return;
|
||||
//creating a popup menu
|
||||
PopupMenu popup = new PopupMenu(context, ibOptions);
|
||||
//inflating menu from xml resource
|
||||
popup.inflate(R.menu.list_context_menu);
|
||||
popupOpen = true;
|
||||
//adding click listener
|
||||
popup.setOnMenuItemClickListener(item -> {
|
||||
if (listener != null) {
|
||||
return listener.onContextInteraction(item, infoItem);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
//displaying the popup
|
||||
popup.show();
|
||||
popup.setOnDismissListener(menu -> popupOpen = false);
|
||||
|
||||
}
|
||||
});
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
@@ -23,7 +23,6 @@ import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
@@ -59,9 +58,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.model.NetworkType;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -77,7 +74,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Helper {
|
||||
@@ -487,7 +483,6 @@ public class Helper {
|
||||
}
|
||||
|
||||
etPassword.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (etPassword.getError() != null) {
|
||||
@@ -511,17 +506,16 @@ public class Helper {
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(context.getString(R.string.label_ok), null)
|
||||
.setNegativeButton(context.getString(R.string.label_cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Helper.hideKeyboardAlways((Activity) context);
|
||||
cancelSignal.cancel();
|
||||
if (passwordTask != null) {
|
||||
passwordTask.cancel(true);
|
||||
passwordTask = null;
|
||||
}
|
||||
dialog.cancel();
|
||||
openDialog = null;
|
||||
(dialog, id) -> {
|
||||
action.fail(wallet);
|
||||
Helper.hideKeyboardAlways((Activity) context);
|
||||
cancelSignal.cancel();
|
||||
if (passwordTask != null) {
|
||||
passwordTask.cancel(true);
|
||||
passwordTask = null;
|
||||
}
|
||||
dialog.cancel();
|
||||
openDialog = null;
|
||||
});
|
||||
openDialog = alertDialogBuilder.create();
|
||||
|
||||
@@ -564,45 +558,37 @@ public class Helper {
|
||||
};
|
||||
}
|
||||
|
||||
openDialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
@Override
|
||||
public void onShow(DialogInterface dialog) {
|
||||
if (fingerprintAuthAllowed && fingerprintAuthCallback != null) {
|
||||
tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icFingerprint, null, null, null);
|
||||
tvOpenPrompt.setText(context.getText(R.string.prompt_fingerprint_auth));
|
||||
tvOpenPrompt.setVisibility(View.VISIBLE);
|
||||
FingerprintHelper.authenticate(context, cancelSignal, fingerprintAuthCallback);
|
||||
} else {
|
||||
etPassword.requestFocus();
|
||||
}
|
||||
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (passwordTask == null) {
|
||||
passwordTask = new PasswordTask(pass, false);
|
||||
passwordTask.execute();
|
||||
}
|
||||
}
|
||||
});
|
||||
openDialog.setOnShowListener(dialog -> {
|
||||
if (fingerprintAuthAllowed && fingerprintAuthCallback != null) {
|
||||
tvOpenPrompt.setCompoundDrawablesRelativeWithIntrinsicBounds(icFingerprint, null, null, null);
|
||||
tvOpenPrompt.setText(context.getText(R.string.prompt_fingerprint_auth));
|
||||
tvOpenPrompt.setVisibility(View.VISIBLE);
|
||||
FingerprintHelper.authenticate(context, cancelSignal, fingerprintAuthCallback);
|
||||
} else {
|
||||
etPassword.requestFocus();
|
||||
}
|
||||
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(view -> {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (passwordTask == null) {
|
||||
passwordTask = new PasswordTask(pass, false);
|
||||
passwordTask.execute();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// accept keyboard "ok"
|
||||
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (passwordTask == null) {
|
||||
passwordTask = new PasswordTask(pass, false);
|
||||
passwordTask.execute();
|
||||
}
|
||||
return true;
|
||||
etPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (passwordTask == null) {
|
||||
passwordTask = new PasswordTask(pass, false);
|
||||
passwordTask.execute();
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (Helper.preventScreenshot()) {
|
||||
@@ -616,7 +602,7 @@ public class Helper {
|
||||
public interface PasswordAction {
|
||||
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) {
|
||||
@@ -625,7 +611,7 @@ public class Helper {
|
||||
action.act(walletName, walletPassword, fingerprintUsed);
|
||||
return true;
|
||||
} else {
|
||||
action.fail(walletName, walletPassword, fingerprintUsed);
|
||||
action.fail(walletName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -121,6 +121,10 @@ public class RestoreHeight {
|
||||
blockheight.put("2020-09-01", 2176790L);
|
||||
blockheight.put("2020-10-01", 2198370L);
|
||||
blockheight.put("2020-11-01", 2220670L);
|
||||
blockheight.put("2020-12-01", 2242241L);
|
||||
blockheight.put("2021-01-01", 2264584L);
|
||||
blockheight.put("2021-02-01", 2286892L);
|
||||
blockheight.put("2021-03-01", 2307079L);
|
||||
}
|
||||
|
||||
public long getHeight(String date) {
|
||||
|
@@ -0,0 +1,73 @@
|
||||
package com.m2049r.xmrwallet.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
|
||||
public class PasswordEntryView extends TextInputLayout implements TextWatcher {
|
||||
final private Zxcvbn zxcvbn = new Zxcvbn();
|
||||
|
||||
public PasswordEntryView(@NonNull Context context) {
|
||||
super(context, null);
|
||||
}
|
||||
|
||||
public PasswordEntryView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs, R.attr.textInputStyle);
|
||||
}
|
||||
|
||||
public PasswordEntryView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView(@NonNull View child, int index, @NonNull final ViewGroup.LayoutParams params) {
|
||||
super.addView(child, index, params);
|
||||
final EditText et = getEditText();
|
||||
if (et != null)
|
||||
et.addTextChangedListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
String password = s.toString();
|
||||
int icon = 0;
|
||||
if (!password.isEmpty()) {
|
||||
final double strength = Math.min(zxcvbn.measure(password).getGuessesLog10(), 15) / 3 * 20; // 0-100%
|
||||
if (strength < 21)
|
||||
icon = R.drawable.ic_smiley_sad_filled;
|
||||
else if (strength < 40)
|
||||
icon = R.drawable.ic_smiley_meh_filled;
|
||||
else if (strength < 60)
|
||||
icon = R.drawable.ic_smiley_neutral_filled;
|
||||
else if (strength < 80)
|
||||
icon = R.drawable.ic_smiley_happy_filled;
|
||||
else if (strength < 99)
|
||||
icon = R.drawable.ic_smiley_ecstatic_filled;
|
||||
else
|
||||
icon = R.drawable.ic_smiley_gunther_filled;
|
||||
}
|
||||
setErrorIconDrawable(icon);
|
||||
if (icon != 0)
|
||||
setError(" ");
|
||||
else setError(null);
|
||||
}
|
||||
}
|
9
app/src/main/res/drawable/ic_smiley_ecstatic_filled.xml
Normal file
9
app/src/main/res/drawable/ic_smiley_ecstatic_filled.xml
Normal file
File diff suppressed because one or more lines are too long
12
app/src/main/res/drawable/ic_smiley_gunther_filled.xml
Normal file
12
app/src/main/res/drawable/ic_smiley_gunther_filled.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="85.43"
|
||||
android:viewportHeight="85.43">
|
||||
<path
|
||||
android:fillColor="@color/monerujoPrimary"
|
||||
android:pathData="M77.39,26A38.49,38.49 0,0 0,8 26Z" />
|
||||
<path
|
||||
android:fillColor="@color/monerujoPrimary"
|
||||
android:pathData="M79.54,33c-2.3,8.19 -9.06,14.12 -17.06,14.12S47.73,41.19 45.43,33L41.34,33C39,41.19 31.93,47.12 23.63,47.12S8.31,41.19 5.92,33L5.46,33A38.5,38.5 0,1 0,80 33ZM34.65,63.54L12.24,63.54C20,51.84 29.09,49 32.86,48.34a8.44,8.44 0,0 1,1.6 -0.21h0.19a7.71,7.71 0,0 1,0 15.42ZM50.42,63.54a7.71,7.71 0,0 1,0 -15.42h0.19a8.34,8.34 0,0 1,1.6 0.21c3.78,0.68 12.87,3.5 20.62,15.2Z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_smiley_happy_filled.xml
Normal file
9
app/src/main/res/drawable/ic_smiley_happy_filled.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="85.43"
|
||||
android:viewportHeight="85.43">
|
||||
<path
|
||||
android:fillColor="@color/monerujoPrimary"
|
||||
android:pathData="M42.8,4.22A38.5,38.5 0,1 0,81.29 42.71,38.54 38.54,0 0,0 42.8,4.22ZM58.21,28.5a5.29,5.29 0,1 1,-5.28 5.28A5.28,5.28 0,0 1,58.21 28.5ZM27.39,28.5a5.29,5.29 0,1 1,-5.29 5.28A5.28,5.28 0,0 1,27.39 28.5ZM64.39,50.11c-1.14,5.82 -7.12,16.83 -21.59,16.83s-20.45,-11 -21.59,-16.83a3.5,3.5 0,1 1,6.87 -1.34c0.23,1.12 2.68,11.17 14.72,11.17 12.39,0 14.63,-10.72 14.72,-11.17a3.5,3.5 0,1 1,6.87 1.34Z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_smiley_meh_filled.xml
Normal file
9
app/src/main/res/drawable/ic_smiley_meh_filled.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="85.43"
|
||||
android:viewportHeight="85.43">
|
||||
<path
|
||||
android:fillColor="@color/monerujoPrimary"
|
||||
android:pathData="M42.71,4.22a38.5,38.5 0,1 0,38.5 38.49A38.54,38.54 0,0 0,42.71 4.22ZM22,33.78a5.28,5.28 0,1 1,5.28 5.29A5.27,5.27 0,0 1,22 33.78ZM58.83,53.78L28,60.24a3.5,3.5 0,0 1,-1.44 -6.85L57.4,46.9a3.5,3.5 0,0 1,1.45 6.85ZM58.11,39.1a5.29,5.29 0,1 1,5.28 -5.29A5.29,5.29 0,0 1,58.13 39.07Z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_smiley_neutral_filled.xml
Normal file
9
app/src/main/res/drawable/ic_smiley_neutral_filled.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="28dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="85.43"
|
||||
android:viewportHeight="85.43">
|
||||
<path
|
||||
android:fillColor="@color/monerujoPrimary"
|
||||
android:pathData="M42.84,4.22a38.5,38.5 0,1 0,38.5 38.49A38.54,38.54 0,0 0,42.84 4.22ZM22.15,33.78a5.28,5.28 0,1 1,5.28 5.29A5.27,5.27 0,0 1,22.15 33.78ZM58.26,56.84L27.43,56.84a3.5,3.5 0,0 1,0 -7L58.26,49.84a3.5,3.5 0,0 1,0 7ZM58.26,39.07a5.29,5.29 0,1 1,5.28 -5.29A5.29,5.29 0,0 1,58.26 39.07Z" />
|
||||
</vector>
|
9
app/src/main/res/drawable/ic_smiley_sad_filled.xml
Normal file
9
app/src/main/res/drawable/ic_smiley_sad_filled.xml
Normal file
File diff suppressed because one or more lines are too long
@@ -29,13 +29,17 @@
|
||||
android:textAlignment="textStart" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
<com.m2049r.xmrwallet.widget.PasswordEntryView
|
||||
android:id="@+id/etWalletPassword"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/header_top_first"
|
||||
app:errorEnabled="true">
|
||||
app:boxStrokeErrorColor="@color/monerujoPrimary"
|
||||
app:errorEnabled="true"
|
||||
app:errorIconDrawable="@drawable/ic_smiley_gunther_filled"
|
||||
app:errorIconTint="@color/monerujoPrimary"
|
||||
app:errorTextColor="@color/monerujoPrimary">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
style="@style/MoneroEdit"
|
||||
@@ -46,7 +50,7 @@
|
||||
android:inputType="textVisiblePassword"
|
||||
android:textAlignment="textStart" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</com.m2049r.xmrwallet.widget.PasswordEntryView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llFingerprintAuth"
|
||||
|
@@ -10,12 +10,16 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
<com.m2049r.xmrwallet.widget.PasswordEntryView
|
||||
android:id="@+id/etWalletPasswordA"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:errorEnabled="true">
|
||||
app:boxStrokeErrorColor="@color/monerujoPrimary"
|
||||
app:errorEnabled="true"
|
||||
app:errorIconDrawable="@drawable/ic_smiley_gunther_filled"
|
||||
app:errorIconTint="@color/monerujoPrimary"
|
||||
app:errorTextColor="@color/monerujoPrimary">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
style="@style/MoneroEdit"
|
||||
@@ -25,7 +29,7 @@
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="textVisiblePassword"
|
||||
android:textAlignment="textStart" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</com.m2049r.xmrwallet.widget.PasswordEntryView>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/etWalletPasswordB"
|
||||
|
@@ -230,5 +230,5 @@
|
||||
<p>詳細的付款資訊將自動填入,請像其他交易一樣,仔細地檢查所填入的資料是否正確。</p>
|
||||
]]></string>
|
||||
|
||||
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
|
||||
<string name="help_ok">我知道了!</string> <!-- Note: "Got it" as in "I understand this" -->
|
||||
</resources>
|
||||
|
@@ -358,57 +358,57 @@
|
||||
<string name="bad_ledger_seed">無效的 Ledger 種子碼!</string>
|
||||
<string name="prompt_ledger_seed_warn">在此輸入 Ledger 種子碼可能存在嚴重的安全風險!</string>
|
||||
|
||||
<string name="label_restoreheight">Restore Height</string>
|
||||
<string name="label_restoreheight">回復高度</string>
|
||||
|
||||
<string name="toast_ledger_start_app">Start Monero App on %1$s</string>
|
||||
<string name="toast_ledger_start_app">在 %1$s 上啟動 Monero APP</string>
|
||||
|
||||
<string name="menu_rescan">Rescan!</string>
|
||||
<string name="menu_rescan">重新掃描!</string>
|
||||
|
||||
<string name="onboarding_agree">I get it!</string>
|
||||
<string name="onboarding_button_next">Next</string>
|
||||
<string name="onboarding_button_ready">I\'m ready!</string>
|
||||
<string name="onboarding_agree">我知道了!</string>
|
||||
<string name="onboarding_button_next">下一步</string>
|
||||
<string name="onboarding_button_ready">我準備好了!</string>
|
||||
|
||||
<string name="onboarding_welcome_title">Welcome to Monerujo!</string>
|
||||
<string name="onboarding_welcome_information">This app allows you to create and use Monero wallets. You can store your sweet moneroj in them.</string>
|
||||
<string name="onboarding_seed_title">Keep your seed safe</string>
|
||||
<string name="onboarding_seed_information">The seed grants full access to whoever has it. If you lose it, we cannot help you recover it and you lose your beloved moneroj.</string>
|
||||
<string name="onboarding_xmrto_title">Send Crypto</string>
|
||||
<string name="onboarding_xmrto_information">Monerujo has SideShift.ai support built-in. Just paste or scan a BTC, LTC, ETH, DASH or DOGE address and you\'ll be sending these cryptos by spending XMR.</string>
|
||||
<string name="onboarding_nodes_title">Nodes, your way</string>
|
||||
<string name="onboarding_nodes_information">Nodes connect you to the Monero network. Choose between public nodes or go full cypherpunk using your own.</string>
|
||||
<string name="onboarding_fpsend_title">Send with fingerprint</string>
|
||||
<string name="onboarding_fpsend_information">You\'re now able to send XMR with just your fingerprint if you enabled it. To request the password, just disable fingerprint access.</string>
|
||||
<string name="onboarding_welcome_title">歡迎使用 Monerujo!</string>
|
||||
<string name="onboarding_welcome_information">這個 APP 可以讓你建立和使用 Monero 錢包。你可以在此儲存你心愛的 moneroj。</string>
|
||||
<string name="onboarding_seed_title">保管好你的種子碼</string>
|
||||
<string name="onboarding_seed_information">種子碼提供任何人完整的權限。如果你遺失了種子碼,我們無法幫你找回它,且你將失去你心愛的 moneroj。</string>
|
||||
<string name="onboarding_xmrto_title">發送加密貨幣</string>
|
||||
<string name="onboarding_xmrto_information">Monerujo 內建 SideShift.ai 服務。只要貼上或掃描 BTC、LTC、ETH、DASH 或 DOGE 地址,你就可以直接使用 XMR 來發送這些加密貨幣。</string>
|
||||
<string name="onboarding_nodes_title">節點選擇,由你作主</string>
|
||||
<string name="onboarding_nodes_information">你必須透過節點連線至 Monero 交易網路。選擇使用公共節點或是很密碼龐克式地使用你自己的節點。</string>
|
||||
<string name="onboarding_fpsend_title">使用指紋辨識發送交易</string>
|
||||
<string name="onboarding_fpsend_information">你可以啟用指紋辨識來讓發送交易更輕鬆。若要使用密碼,只需停用指紋辨識功能即可。</string>
|
||||
|
||||
<string name="menu_daynight">Dark Mode</string>
|
||||
<string name="menu_daynight">深色模式</string>
|
||||
<string-array name="daynight_themes">
|
||||
<item>Auto</item>
|
||||
<item>Day</item>
|
||||
<item>Night</item>
|
||||
<item>自動</item>
|
||||
<item>白天</item>
|
||||
<item>黑夜</item>
|
||||
</string-array>
|
||||
<string name="gunther_says">There is nothing here\nPlease create or restore a wallet</string>
|
||||
<string name="gunther_says">這裡還沒有東西\n請建立或是回復一個錢包</string>
|
||||
|
||||
<string name="menu_default_nodes">Restore default nodes</string>
|
||||
<string name="toast_default_nodes">Restoring already in progress…</string>
|
||||
<string name="menu_default_nodes">恢復為預設節點</string>
|
||||
<string name="toast_default_nodes">已經正在恢復</string>
|
||||
|
||||
<string name="node_updated_now">Last Block: %1$d seconds ago</string>
|
||||
<string name="node_updated_mins">Last Block: %1$d minutes ago</string>
|
||||
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
|
||||
<string name="node_updated_days">Last Block: %1$d days ago</string>
|
||||
<string name="node_updated_now">最後區塊: %1$d 秒鐘前</string>
|
||||
<string name="node_updated_mins">最後區塊: %1$d 分鐘前</string>
|
||||
<string name="node_updated_hours">最後區塊: %1$d 小時前</string>
|
||||
<string name="node_updated_days">最後區塊: %1$d 天前</string>
|
||||
|
||||
<string name="shift_noquote">Cannot get quote</string>
|
||||
<string name="shift_checkamount">Check amount and try again</string>
|
||||
<string name="shift_noquote">無法取得報價</string>
|
||||
<string name="shift_checkamount">確認金額後再試一次</string>
|
||||
|
||||
<string name="info_xmrto_ambiguous"><![CDATA[
|
||||
<b>Ambiguous address.</b><br/>
|
||||
<i>Please select the type above.</i>
|
||||
<b>有岐義的地址。</b><br/>
|
||||
<i>請從上面選擇類型。</i>
|
||||
]]></string>
|
||||
|
||||
<string name="info_xmrto_help"><![CDATA[
|
||||
<b>Please enter or scan a %1$s address.</b><br/>
|
||||
<i>You'll send XMR and the receiver will get %2$s using the <b>SideShift.ai</b> service.</i>
|
||||
<b>請輸入或是掃描 %1$s 的地址。</b><br/>
|
||||
<i>你'將會發送 XMR 而接受者將會收到由 <b>SideShift.ai</b> 服務兌換的 %2$s 。</i>
|
||||
]]></string>
|
||||
|
||||
<string name="info_xmrto_help_xmr"><![CDATA[
|
||||
<b>Please enter or scan a Monero address.</b>
|
||||
<b>請輸入或掃描一個 Monero 地址。</b>
|
||||
]]></string>
|
||||
</resources>
|
||||
|
@@ -1,132 +1,3 @@
|
||||
# BUILDING external libs
|
||||
|
||||
Based on https://forum.getmonero.org/5/support/87643/building-monero-v0-10-3-1-for-android and the internet.
|
||||
|
||||
Do not follow this blindly.
|
||||
|
||||
These instructions are tailored to building ```wallep_api```.
|
||||
|
||||
These instructions build all supported architectures: ```'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'```.
|
||||
|
||||
Yes, lots of copy&paste here. TODO: Script this.
|
||||
|
||||
## Prepare Ubuntu environment
|
||||
|
||||
```Shell
|
||||
sudo apt-get install build-essential cmake tofrodos libtool-bin
|
||||
sudo mkdir /opt/android
|
||||
sudo chown $LOGNAME /opt/android
|
||||
```
|
||||
|
||||
## Install Android NDK
|
||||
```Shell
|
||||
cd /opt/android
|
||||
wget https://dl.google.com/android/repository/android-ndk-r16b-linux-x86_64.zip
|
||||
unzip android-ndk-r16b-linux-x86_64.zip
|
||||
ln -s android-ndk-r16b ndk
|
||||
ndk/build/tools/make_standalone_toolchain.py --api 21 --stl=libc++ --arch arm --install-dir /opt/android/tool/arm
|
||||
ndk/build/tools/make_standalone_toolchain.py --api 21 --stl=libc++ --arch arm64 --install-dir /opt/android/tool/arm64
|
||||
ndk/build/tools/make_standalone_toolchain.py --api 21 --stl=libc++ --arch x86 --install-dir /opt/android/tool/x86
|
||||
ndk/build/tools/make_standalone_toolchain.py --api 21 --stl=libc++ --arch x86_64 --install-dir /opt/android/tool/x86_64
|
||||
```
|
||||
|
||||
## Prepare output
|
||||
```Shell
|
||||
mkdir -p /opt/android/build
|
||||
```
|
||||
|
||||
## Build OpenSSL
|
||||
Best is to compile openssl from sources. Copying from your phone or elsewhere (don't!) ends up in misery.
|
||||
|
||||
If you don't want to build for all architectures, edit ```build-all-arch.sh``` before running it (Line 12).
|
||||
|
||||
```Shell
|
||||
cd /opt/android
|
||||
git clone https://github.com/m2049r/android-openssl.git
|
||||
wget https://github.com/openssl/openssl/archive/OpenSSL_1_0_2l.tar.gz
|
||||
cd android-openssl
|
||||
tar xfz ../OpenSSL_1_0_2l.tar.gz
|
||||
PATH="${PATH}:/opt/android/tool/arm/bin/:/opt/android/tool/arm64/bin/:/opt/android/tool/x86/bin/:/opt/android/tool/x86_64/bin" \
|
||||
ANDROID_NDK_ROOT=/opt/android/ndk ./build-all-arch.sh
|
||||
```
|
||||
|
||||
### Install & make symlinks
|
||||
```Shell
|
||||
mkdir -p /opt/android/build/openssl/{arm,arm64,x86,x86_64}
|
||||
cp -a /opt/android/android-openssl/prebuilt/armeabi /opt/android/build/openssl/arm/lib
|
||||
cp -a /opt/android/android-openssl/prebuilt/arm64-v8a /opt/android/build/openssl/arm64/lib
|
||||
cp -a /opt/android/android-openssl/prebuilt/x86 /opt/android/build/openssl/x86/lib
|
||||
cp -a /opt/android/android-openssl/prebuilt/x86_64 /opt/android/build/openssl/x86_64/lib
|
||||
cp -aL /opt/android/android-openssl/openssl-OpenSSL_1_0_2l/include/openssl/ /opt/android/build/openssl/include
|
||||
ln -s /opt/android/build/openssl/include /opt/android/build/openssl/arm/include
|
||||
ln -s /opt/android/build/openssl/include /opt/android/build/openssl/arm64/include
|
||||
ln -s /opt/android/build/openssl/include /opt/android/build/openssl/x86/include
|
||||
ln -s /opt/android/build/openssl/include /opt/android/build/openssl/x86_64/include
|
||||
```
|
||||
```Shell
|
||||
ln -sf /opt/android/build/openssl/include /opt/android/tool/arm/sysroot/usr/include/openssl
|
||||
ln -sf /opt/android/build/openssl/arm/lib/*.so /opt/android/tool/arm/sysroot/usr/lib
|
||||
|
||||
ln -sf /opt/android/build/openssl/include /opt/android/tool/arm64/sysroot/usr/include/openssl
|
||||
ln -sf /opt/android/build/openssl/arm64/lib/*.so /opt/android/tool/arm64/sysroot/usr/lib
|
||||
|
||||
ln -sf /opt/android/build/openssl/include /opt/android/tool/x86/sysroot/usr/include/openssl
|
||||
ln -sf /opt/android/build/openssl/x86/lib/*.so /opt/android/tool/x86/sysroot/usr/lib
|
||||
|
||||
ln -sf /opt/android/build/openssl/include /opt/android/tool/x86_64/sysroot/usr/include/openssl
|
||||
ln -sf /opt/android/build/openssl/x86_64/lib/*.so /opt/android/tool/x86_64/sysroot/usr/lib64
|
||||
```
|
||||
|
||||
## Build Boost
|
||||
```Shell
|
||||
cd /opt/android
|
||||
wget https://sourceforge.net/projects/boost/files/boost/1.58.0/boost_1_58_0.tar.gz/download -O boost_1_58_0.tar.gz
|
||||
tar xfz boost_1_58_0.tar.gz
|
||||
cd boost_1_58_0
|
||||
./bootstrap.sh
|
||||
```
|
||||
Comment out ```using ::fgetpos;``` & ```using ::fsetpos;``` in ```cstdio```.
|
||||
```
|
||||
sed -ibackup "s|using ::fgetpos;|//using ::fgetpos;|" /opt/android/tool/arm/include/c++/4.9.x/cstdio
|
||||
sed -ibackup "s|using ::fsetpos;|//using ::fsetpos;|" /opt/android/tool/arm/include/c++/4.9.x/cstdio
|
||||
sed -ibackup "s|using ::fgetpos;|//using ::fgetpos;|" /opt/android/tool/arm64/include/c++/4.9.x/cstdio
|
||||
sed -ibackup "s|using ::fsetpos;|//using ::fsetpos;|" /opt/android/tool/arm64/include/c++/4.9.x/cstdio
|
||||
sed -ibackup "s|using ::fgetpos;|//using ::fgetpos;|" /opt/android/tool/x86/include/c++/4.9.x/cstdio
|
||||
sed -ibackup "s|using ::fsetpos;|//using ::fsetpos;|" /opt/android/tool/x86/include/c++/4.9.x/cstdio
|
||||
sed -ibackup "s|using ::fgetpos;|//using ::fgetpos;|" /opt/android/tool/x86_64/include/c++/4.9.x/cstdio
|
||||
sed -ibackup "s|using ::fsetpos;|//using ::fsetpos;|" /opt/android/tool/x86_64/include/c++/4.9.x/cstdio
|
||||
```
|
||||
|
||||
Then build & install to ```/opt/android/build/boost``` with
|
||||
```Shell
|
||||
PATH=/opt/android/tool/arm/arm-linux-androideabi/bin:/opt/android/tool/arm/bin:$PATH ./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --build-dir=android-arm --prefix=/opt/android/build/boost/arm --includedir=/opt/android/build/boost/include toolset=clang threading=multi threadapi=pthread target-os=android install
|
||||
ln -sf ../include /opt/android/build/boost/arm
|
||||
PATH=/opt/android/tool/arm64/aarch64-linux-android/bin:/opt/android/tool/arm64/bin:$PATH ./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --build-dir=android-arm64 --prefix=/opt/android/build/boost/arm64 --includedir=/opt/android/build/boost/include toolset=clang threading=multi threadapi=pthread target-os=android install
|
||||
ln -sf ../include /opt/android/build/boost/arm64
|
||||
PATH=/opt/android/tool/x86/i686-linux-android/bin:/opt/android/tool/x86/bin:$PATH ./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --build-dir=android-x86 --prefix=/opt/android/build/boost/x86 --includedir=/opt/android/build/boost/include toolset=clang threading=multi threadapi=pthread target-os=android install
|
||||
ln -sf ../include /opt/android/build/boost/x86
|
||||
PATH=/opt/android/tool/x86_64/x86_64-linux-android/bin:/opt/android/tool/x86_64/bin:$PATH ./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --build-dir=android-x86_64 --prefix=/opt/android/build/boost/x86_64 --includedir=/opt/android/build/boost/include toolset=clang threading=multi threadapi=pthread target-os=android install
|
||||
ln -sf ../include /opt/android/build/boost/x86_64
|
||||
```
|
||||
|
||||
## And finally: Build Monero
|
||||
```Shell
|
||||
cd /opt/android
|
||||
git clone https://github.com/m2049r/monero.git
|
||||
cd monero
|
||||
git checkout release-v0.12.3
|
||||
|
||||
./build-all-arch.sh
|
||||
```
|
||||
|
||||
# Bringing it all together
|
||||
- Copy all .a libraries into the appropriate `external-libs` folders.
|
||||
- Copy `/opt/android/monero/src/wallet/api/wallet2_api.h` into `external-libs/monero/include`
|
||||
|
||||
If using default locations, this would mean:
|
||||
```Shell
|
||||
cd <path-to-xmrwallet>/external-libs
|
||||
# remove old stuff
|
||||
find . -name "*.a" -or -name "*.h" -type f -delete
|
||||
./collect.sh
|
||||
```
|
||||
Make a symbol link named `monero` to the monero repo (cloned from https://github.com/m2049r/monero) & then `make` in the `external-libs` directory. This builds all binaries using the supplied Dockerfiles so make sure you have docker up and running.
|
||||
|
Reference in New Issue
Block a user