1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-04 17:28:42 +02:00

Compare commits

...

12 Commits

Author SHA1 Message Date
m2049r
faf57c96fc add more output currencies (#714) 2021-02-15 21:38:26 +01:00
m2049r
57ddddfce2 replace xmr.to with SideShift.ai (#710)
replace xmr.to with SideShift.ai
random bugfixes
upgrade gradle & dependencies
2021-02-13 00:01:19 +01:00
netrik182
ab6069058b new pt-br strings after review (#708) 2021-02-01 23:02:36 +01:00
m2049r
d94d6e6925 downgrade to APK 29 & bump version 2020-11-22 19:39:30 +01:00
m2049r
4a819cc159 bump version 2020-11-21 22:18:36 +01:00
m2049r
f40c3d6c6d catch cases where the qr code produces a null monero address (#702) 2020-11-21 22:08:23 +01:00
m2049r
82b25df7ad better node selection (#701) 2020-11-21 21:54:21 +01:00
m2049r
08f815e830 don't show connection error if we havent tested yet (#700) 2020-11-21 13:22:54 +01:00
m2049r
4e59be2dff New handling of nodes (#699)
* rework node handling

* update block heights
2020-11-19 16:36:01 +01:00
m2049r
45c5883e11 fix potential NPE in updateAccountsList() 2020-10-20 23:47:41 +02:00
m2049r
067a23e6a5 fix crash if back before new address is generated 2020-10-20 23:40:55 +02:00
m2049r
d21fd41c44 update dependencies & fix MethodNotFound exception 2020-10-20 23:31:29 +02:00
190 changed files with 5822 additions and 4817 deletions

View File

@@ -1,14 +1,14 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion '29.0.2' buildToolsVersion '29.0.2'
defaultConfig { defaultConfig {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 29
versionCode 503 versionCode 705
versionName "1.15.3 'Dark Fork'" versionName "1.17.5.1 'Druk'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@@ -56,6 +56,9 @@ android {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
} }
applicationVariants.all { variant ->
variant.buildConfigField "String", "ID_A", "\"" + getId("ID_A") + "\""
}
} }
externalNativeBuild { externalNativeBuild {
@@ -109,20 +112,26 @@ android {
} }
} }
def getId(name) {
def Properties props = new Properties()
props.load(new FileInputStream(new File('monerujo.id')))
return props[name]
}
dependencies { dependencies {
implementation 'androidx.core:core:1.3.2' implementation 'androidx.core:core:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation group: 'com.google.android.material', name: 'material', version: '1.3.0-alpha03' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" implementation "com.squareup.okhttp3:okhttp:4.9.0"
implementation "com.burgstaller:okhttp-digest:2.0" implementation "com.burgstaller:okhttp-digest:2.1"
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion" implementation "com.jakewharton.timber:timber:4.7.1"
implementation 'com.nulab-inc:zxcvbn:1.3.0' implementation 'com.nulab-inc:zxcvbn:1.3.0'
@@ -131,12 +140,14 @@ dependencies {
implementation 'org.slf4j:slf4j-nop:1.7.30' implementation 'org.slf4j:slf4j-nop:1.7.30'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2' implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
// https://mvnrepository.com/artifact/com.github.aelstad/keccakj
implementation 'com.github.aelstad:keccakj:1.1.0' implementation 'com.github.aelstad:keccakj:1.1.0'
testImplementation "junit:junit:$rootProject.ext.junitVersion" testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
testImplementation 'org.json:json:20180813' testImplementation 'org.json:json:20180813'
testImplementation 'net.jodah:concurrentunit:0.4.4' testImplementation 'net.jodah:concurrentunit:0.4.4'
compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'
} }

View File

@@ -13,6 +13,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:requestLegacyExternalStorage="true"
android:name=".XmrWalletApplication" android:name=".XmrWalletApplication"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@@ -185,7 +185,6 @@ public class Dispatcher implements PeerRetriever.OnGetPeers {
public void seedPeers(Collection<NodeInfo> seedNodes) { public void seedPeers(Collection<NodeInfo> seedNodes) {
for (NodeInfo node : seedNodes) { for (NodeInfo node : seedNodes) {
node.clear();
if (node.isFavourite()) { if (node.isFavourite()) {
rpcNodes.add(node); rpcNodes.add(node);
if (listener != null) listener.onGet(node); if (listener != null) listener.onGet(node);

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2018 m2049r
*
* 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.levin.scanner; package com.m2049r.levin.scanner;
import java.net.InetAddress; import java.net.InetAddress;

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2017-2020 m2049r et al.
*
* 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; package com.m2049r.xmrwallet;
import android.app.PendingIntent; import android.app.PendingIntent;
@@ -197,7 +213,7 @@ public class BaseActivity extends SecureActivity implements GenerateReviewFragme
if (uri == null) { if (uri == null) {
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show(); Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
} else { } else {
BarcodeData bc = BarcodeData.fromQrCode(uri.toString()); BarcodeData bc = BarcodeData.fromString(uri.toString());
if (bc == null) if (bc == null)
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show(); Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
else else

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,7 @@ import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.Notice; import com.m2049r.xmrwallet.util.Notice;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@@ -105,6 +106,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Set<NodeInfo> getFavouriteNodes(); Set<NodeInfo> getFavouriteNodes();
Set<NodeInfo> getOrPopulateFavourites();
boolean hasLedger(); boolean hasLedger();
} }
@@ -132,11 +135,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
activityCallback.setTitle(null); activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS); activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet(); activityCallback.showNet();
NodeInfo node = activityCallback.getNode(); pingSelectedNode();
if (node == null)
findBestNode();
else
showNode(node);
} }
@Override @Override
@@ -186,23 +185,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
pbNode = view.findViewById(R.id.pbNode); pbNode = view.findViewById(R.id.pbNode);
llNode = view.findViewById(R.id.llNode); llNode = view.findViewById(R.id.llNode);
llNode.setOnClickListener(new View.OnClickListener() { llNode.setOnClickListener(v -> startNodePrefs());
@Override
public void onClick(View v) {
if (activityCallback.getFavouriteNodes().isEmpty())
startNodePrefs();
else
findBestNode();
}
});
tvNodeName = view.findViewById(R.id.tvNodeName); tvNodeName = view.findViewById(R.id.tvNodeName);
tvNodeAddress = view.findViewById(R.id.tvNodeAddress); tvNodeAddress = view.findViewById(R.id.tvNodeAddress);
view.findViewById(R.id.ibOption).setOnClickListener(new View.OnClickListener() { view.findViewById(R.id.ibRenew).setOnClickListener(v -> findBestNode());
@Override
public void onClick(View v) {
startNodePrefs();
}
});
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@@ -421,32 +407,57 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
} }
public void findBestNode() { public void findBestNode() {
new AsyncFindBestNode().execute(); new AsyncFindBestNode().execute(AsyncFindBestNode.FIND_BEST);
} }
private class AsyncFindBestNode extends AsyncTask<Void, Void, NodeInfo> { public void pingSelectedNode() {
new AsyncFindBestNode().execute(AsyncFindBestNode.PING_SELECTED);
}
private NodeInfo autoselect(Set<NodeInfo> nodes) {
if (nodes.isEmpty()) return null;
NodePinger.execute(nodes, null);
List<NodeInfo> nodeList = new ArrayList<>(nodes);
Collections.sort(nodeList, NodeInfo.BestNodeComparator);
return nodeList.get(0);
}
private class AsyncFindBestNode extends AsyncTask<Integer, Void, NodeInfo> {
final static int PING_SELECTED = 0;
final static int FIND_BEST = 1;
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
pbNode.setVisibility(View.VISIBLE); pbNode.setVisibility(View.VISIBLE);
llNode.setVisibility(View.INVISIBLE); llNode.setVisibility(View.INVISIBLE);
activityCallback.setNode(null);
} }
@Override @Override
protected NodeInfo doInBackground(Void... params) { protected NodeInfo doInBackground(Integer... params) {
List<NodeInfo> nodesToTest = new ArrayList<>(activityCallback.getFavouriteNodes()); Set<NodeInfo> favourites = activityCallback.getOrPopulateFavourites();
Timber.d("testing best node from %d", nodesToTest.size()); NodeInfo selectedNode;
if (nodesToTest.isEmpty()) return null; if (params[0] == FIND_BEST) {
for (NodeInfo node : nodesToTest) { selectedNode = autoselect(favourites);
node.testRpcService(); // TODO: do this in parallel? } else if (params[0] == PING_SELECTED) {
// no: it's better if it looks like it's doing something selectedNode = activityCallback.getNode();
} if (!activityCallback.getFavouriteNodes().contains(selectedNode))
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator); selectedNode = null; // it's not in the favourites (any longer)
NodeInfo bestNode = nodesToTest.get(0); if (selectedNode == null)
if (bestNode.isValid()) { for (NodeInfo node : favourites) {
activityCallback.setNode(bestNode); if (node.isSelected()) {
return bestNode; selectedNode = node;
break;
}
}
if (selectedNode == null) { // autoselect
selectedNode = autoselect(favourites);
} else
selectedNode.testRpcService();
} else throw new IllegalStateException();
if ((selectedNode != null) && selectedNode.isValid()) {
activityCallback.setNode(selectedNode);
return selectedNode;
} else { } else {
activityCallback.setNode(null); activityCallback.setNode(null);
return null; return null;
@@ -462,17 +473,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Timber.d("found a good node %s", result.toString()); Timber.d("found a good node %s", result.toString());
showNode(result); showNode(result);
} else { } else {
if (!activityCallback.getFavouriteNodes().isEmpty()) { tvNodeName.setText(getResources().getText(R.string.node_create_hint));
tvNodeName.setText(getResources().getText(R.string.node_refresh_hint)); tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_refresh_black_24dp, 0, 0, 0); tvNodeAddress.setText(null);
tvNodeAddress.setText(null); tvNodeAddress.setVisibility(View.GONE);
tvNodeAddress.setVisibility(View.GONE);
} else {
tvNodeName.setText(getResources().getText(R.string.node_create_hint));
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
tvNodeAddress.setText(null);
tvNodeAddress.setVisibility(View.GONE);
}
} }
} }
@@ -485,12 +489,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private void showNode(NodeInfo nodeInfo) { private void showNode(NodeInfo nodeInfo) {
tvNodeName.setText(nodeInfo.getName()); tvNodeName.setText(nodeInfo.getName());
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0); tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0);
tvNodeAddress.setText(nodeInfo.getAddress()); Helper.showTimeDifference(tvNodeAddress, nodeInfo.getTimestamp());
tvNodeAddress.setVisibility(View.VISIBLE); tvNodeAddress.setVisibility(View.VISIBLE);
} }
private void startNodePrefs() { private void startNodePrefs() {
activityCallback.setNode(null);
activityCallback.onNodePrefs(); activityCallback.onNodePrefs();
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,9 @@ import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix; import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
@@ -468,7 +470,7 @@ public class ReceiveFragment extends Fragment {
Timber.d("CLEARQR"); Timber.d("CLEARQR");
return; return;
} }
bcData = new BarcodeData(BarcodeData.Asset.XMR, address, null, notes, xmrAmount); bcData = new BarcodeData(Crypto.XMR, address, notes, xmrAmount);
int size = Math.max(ivQrCode.getWidth(), ivQrCode.getHeight()); int size = Math.max(ivQrCode.getWidth(), ivQrCode.getHeight());
Bitmap qr = generate(bcData.getUriString(), size, size); Bitmap qr = generate(bcData.getUriString(), size, size);
if (qr != null) { if (qr != null) {
@@ -610,6 +612,8 @@ public class ReceiveFragment extends Fragment {
super.onPostExecute(result); super.onPostExecute(result);
if (dialogOpened) if (dialogOpened)
progressCallback.dismissProgressDialog(); progressCallback.dismissProgressDialog();
if (!isAdded()) // never mind then
return;
tvAddress.setText(newSubaddress); tvAddress.setText(newSubaddress);
tvAddressLabel.setText(getString(R.string.generate_address_label_sub, tvAddressLabel.setText(getString(R.string.generate_address_label_sub,
wallet.getNumSubaddresses() - 1)); wallet.getNumSubaddresses() - 1));

View File

@@ -73,14 +73,7 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
// * On older devices continuously stopping and resuming camera preview can result in freezing the app. // * On older devices continuously stopping and resuming camera preview can result in freezing the app.
// * I don't know why this is the case but I don't have the time to figure out. // * I don't know why this is the case but I don't have the time to figure out.
Handler handler = new Handler(); Handler handler = new Handler();
handler.postDelayed(new handler.postDelayed(() -> mScannerView.resumeCameraPreview(ScannerFragment.this), 2000);
Runnable() {
@Override
public void run() {
mScannerView.resumeCameraPreview(ScannerFragment.this);
}
}, 2000);
} }
@Override @Override

View File

@@ -16,8 +16,11 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.InputType; import android.text.InputType;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -25,6 +28,7 @@ import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -77,6 +81,9 @@ public class TxFragment extends Fragment {
private TextView tvTxXmrToKey; private TextView tvTxXmrToKey;
private TextView tvDestinationBtc; private TextView tvDestinationBtc;
private TextView tvTxAmountBtc; private TextView tvTxAmountBtc;
private TextView tvXmrToSupport;
private TextView tvXmrToKeyLabel;
private ImageView tvXmrToLogo;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -88,6 +95,9 @@ public class TxFragment extends Fragment {
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey); tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc); tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc); tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport);
tvXmrToKeyLabel = view.findViewById(R.id.tvXmrToKeyLabel);
tvXmrToLogo = view.findViewById(R.id.tvXmrToLogo);
tvAccount = view.findViewById(R.id.tvAccount); tvAccount = view.findViewById(R.id.tvAccount);
tvAddress = view.findViewById(R.id.tvAddress); tvAddress = view.findViewById(R.id.tvAddress);
@@ -104,12 +114,9 @@ public class TxFragment extends Fragment {
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() { tvTxXmrToKey.setOnClickListener(v -> {
@Override Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
public void onClick(View v) { Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
}
}); });
Bundle args = getArguments(); Bundle args = getArguments();
@@ -283,12 +290,36 @@ public class TxFragment extends Fragment {
showBtcInfo(); showBtcInfo();
} }
@SuppressLint("SetTextI18n")
void showBtcInfo() { void showBtcInfo() {
if (userNotes.xmrtoKey != null) { if (userNotes.xmrtoKey != null) {
cvXmrTo.setVisibility(View.VISIBLE); cvXmrTo.setVisibility(View.VISIBLE);
tvTxXmrToKey.setText(userNotes.xmrtoKey); String key = userNotes.xmrtoKey;
if ("xmrto".equals(userNotes.xmrtoTag)) { // legacy xmr.to service :(
key = "xmrto-" + key;
}
tvTxXmrToKey.setText(key);
tvDestinationBtc.setText(userNotes.xmrtoDestination); tvDestinationBtc.setText(userNotes.xmrtoDestination);
tvTxAmountBtc.setText(userNotes.xmrtoAmount + " BTC"); tvTxAmountBtc.setText(userNotes.xmrtoAmount + " "+ userNotes.xmrtoCurrency);
switch (userNotes.xmrtoTag) {
case "xmrto":
tvXmrToSupport.setVisibility(View.GONE);
tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
tvXmrToLogo.setImageResource(R.drawable.ic_xmrto_logo);
break;
case "side": // defaults in layout - just add underline
tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
tvXmrToSupport.setOnClickListener(v -> {
Uri uri = Uri.parse("https://sideshift.ai/orders/" + userNotes.xmrtoKey);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
});
break;
default:
tvXmrToSupport.setVisibility(View.GONE);
tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
tvXmrToLogo.setVisibility(View.GONE);
}
} else { } else {
cvXmrTo.setVisibility(View.GONE); cvXmrTo.setVisibility(View.GONE);
} }

View File

@@ -579,11 +579,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}); });
if (numAccounts != wallet.getNumAccounts()) { if (numAccounts != wallet.getNumAccounts()) {
numAccounts = wallet.getNumAccounts(); numAccounts = wallet.getNumAccounts();
runOnUiThread(new Runnable() { runOnUiThread(this::updateAccountsList);
public void run() {
updateAccountsList();
}
});
} }
try { try {
final WalletFragment walletFragment = (WalletFragment) final WalletFragment walletFragment = (WalletFragment)
@@ -925,7 +921,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
public boolean onScanned(String qrCode) { public boolean onScanned(String qrCode) {
// #gurke // #gurke
BarcodeData bcData = BarcodeData.fromQrCode(qrCode); BarcodeData bcData = BarcodeData.fromString(qrCode);
if (bcData != null) { if (bcData != null) {
popFragmentStack(null); popFragmentStack(null);
Timber.d("AAA"); Timber.d("AAA");
@@ -1055,21 +1051,23 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
void updateAccountsList() { void updateAccountsList() {
final Wallet wallet = getWallet();
Menu menu = accountsView.getMenu(); Menu menu = accountsView.getMenu();
menu.removeGroup(R.id.accounts_list); menu.removeGroup(R.id.accounts_list);
final int n = wallet.getNumAccounts(); final Wallet wallet = getWallet();
final boolean showBalances = (n > 1) && !isStreetMode(); if (wallet != null) {
for (int i = 0; i < n; i++) { final int n = wallet.getNumAccounts();
final String label = (showBalances ? final boolean showBalances = (n > 1) && !isStreetMode();
getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2)) for (int i = 0; i < n; i++) {
: wallet.getAccountLabel(i)); final String label = (showBalances ?
final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label); getString(R.string.label_account, wallet.getAccountLabel(i), Helper.getDisplayAmount(wallet.getBalance(i), 2))
item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp); : wallet.getAccountLabel(i));
if (i == wallet.getAccountIndex()) final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label);
item.setChecked(true); item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp);
if (i == wallet.getAccountIndex())
item.setChecked(true);
}
menu.setGroupCheckable(R.id.accounts_list, true, true);
} }
menu.setGroupCheckable(R.id.accounts_list, true, true);
} }
@Override @Override

View File

@@ -49,6 +49,7 @@ 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.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.NumberFormat; import java.text.NumberFormat;
@@ -242,7 +243,7 @@ public class WalletFragment extends Fragment
String balanceCurrency = Helper.BASE_CRYPTO; String balanceCurrency = Helper.BASE_CRYPTO;
double balanceRate = 1.0; double balanceRate = 1.0;
private final ExchangeApi exchangeApi = Helper.getExchangeApi(); private final ExchangeApi exchangeApi = ServiceHelper.getExchangeApi();
void refreshBalance() { void refreshBalance() {
double unconfirmedXmr = Helper.getDecimalAmount(balance - unlockedBalance).doubleValue(); double unconfirmedXmr = Helper.getDecimalAmount(balance - unlockedBalance).doubleValue();

View File

@@ -16,14 +16,13 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.DayNightMode;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper; import com.m2049r.xmrwallet.util.NightmodeHelper;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
package com.m2049r.xmrwallet.data;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.validator.BitcoinAddressType;
import com.m2049r.xmrwallet.util.validator.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.validator.EthAddressValidator;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum Crypto {
XMR("XMR", true, "monero:tx_amount:recipient_name:tx_description", R.id.ibXMR, R.drawable.ic_monero, R.drawable.ic_monero_bw, Wallet::isAddressValid),
BTC("BTC", true, "bitcoin:amount:label:message", R.id.ibBTC, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.BTC);
}),
DASH("DASH", true, "dash:amount:label:message", R.id.ibDASH, R.drawable.ic_xmrto_dash, R.drawable.ic_xmrto_dash_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.DASH);
}),
DOGE("DOGE", true, "dogecoin:amount:label:message", R.id.ibDOGE, R.drawable.ic_xmrto_doge, R.drawable.ic_xmrto_doge_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.DOGE);
}),
ETH("ETH", false, "ethereum:amount:label:message", R.id.ibETH, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, EthAddressValidator::validate),
LTC("LTC", true, "litecoin:amount:label:message", R.id.ibLTC, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.LTC);
});
@Getter
@NonNull
private final String symbol;
@Getter
private final boolean casefull;
@NonNull
private final String uriSpec;
@Getter
private final int buttonId;
@Getter
private final int iconEnabledId;
@Getter
private final int iconDisabledId;
@NonNull
private final Validator validator;
@Nullable
public static Crypto withScheme(@NonNull String scheme) {
for (Crypto crypto : values()) {
if (crypto.getUriScheme().equals(scheme)) return crypto;
}
return null;
}
@Nullable
public static Crypto withSymbol(@NonNull String symbol) {
final String upperSymbol = symbol.toUpperCase();
for (Crypto crypto : values()) {
if (crypto.symbol.equals(upperSymbol)) return crypto;
}
return null;
}
interface Validator {
boolean validate(String address);
}
// TODO maybe cache these segments
String getUriScheme() {
return uriSpec.split(":")[0];
}
String getUriAmount() {
return uriSpec.split(":")[1];
}
String getUriLabel() {
return uriSpec.split(":")[2];
}
String getUriMessage() {
return uriSpec.split(":")[3];
}
boolean validate(String address) {
return validator.validate(address);
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2020 m2049r
*
* 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.data;
import lombok.AllArgsConstructor;
import lombok.Getter;
// Nodes stolen from https://moneroworld.com/#nodes
@AllArgsConstructor
public enum DefaultNodes {
MONERUJO("nodex.monerujo.io:18081"),
XMRTO("node.xmr.to:18081"),
SUPPORTXMR("node.supportxmr.com:18081"),
HASHVAULT("nodes.hashvault.pro:18081"),
MONEROWORLD("node.moneroworld.com:18089"),
XMRTW("opennode.xmr-tw.org:18089");
@Getter
private final String uri;
}

View File

@@ -26,6 +26,8 @@ import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import lombok.Getter;
import lombok.Setter;
import timber.log.Timber; import timber.log.Timber;
public class Node { public class Node {
@@ -33,15 +35,29 @@ public class Node {
static public final String STAGENET = "stagenet"; static public final String STAGENET = "stagenet";
static public final String TESTNET = "testnet"; static public final String TESTNET = "testnet";
@Getter
private String name = null; private String name = null;
@Getter
final private NetworkType networkType; final private NetworkType networkType;
InetAddress hostAddress; InetAddress hostAddress;
@Getter
private String host; private String host;
@Getter
@Setter
int rpcPort = 0; int rpcPort = 0;
private int levinPort = 0; private int levinPort = 0;
@Getter
@Setter
private String username = ""; private String username = "";
@Getter
@Setter
private String password = ""; private String password = "";
@Getter
@Setter
private boolean favourite = false; private boolean favourite = false;
@Getter
@Setter
private boolean selected = false;
@Override @Override
public int hashCode() { public int hashCode() {
@@ -193,7 +209,6 @@ public class Node {
this.levinPort = socketAddress.getPort(); this.levinPort = socketAddress.getPort();
this.username = ""; this.username = "";
this.password = ""; this.password = "";
//this.name = socketAddress.getHostName(); // triggers DNS so we don't do it by default
} }
public String getAddress() { public String getAddress() {
@@ -204,14 +219,6 @@ public class Node {
return hostAddress.getHostAddress(); return hostAddress.getHostAddress();
} }
public String getHost() {
return host;
}
public int getRpcPort() {
return rpcPort;
}
public void setHost(String host) throws UnknownHostException { public void setHost(String host) throws UnknownHostException {
if ((host == null) || (host.isEmpty())) if ((host == null) || (host.isEmpty()))
throw new UnknownHostException("loopback not supported (yet?)"); throw new UnknownHostException("loopback not supported (yet?)");
@@ -219,18 +226,6 @@ public class Node {
this.hostAddress = InetAddress.getByName(host); this.hostAddress = InetAddress.getByName(host);
} }
public void setUsername(String user) {
username = user;
}
public void setPassword(String pass) {
password = pass;
}
public void setRpcPort(int port) {
this.rpcPort = port;
}
public void setName() { public void setName() {
if (name == null) if (name == null)
this.name = hostAddress.getHostName(); this.name = hostAddress.getHostName();
@@ -243,30 +238,6 @@ public class Node {
this.name = name; this.name = name;
} }
public String getName() {
return name;
}
public NetworkType getNetworkType() {
return networkType;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public boolean isFavourite() {
return favourite;
}
public void setFavourite(boolean favourite) {
this.favourite = favourite;
}
public void toggleFavourite() { public void toggleFavourite() {
favourite = !favourite; favourite = !favourite;
} }

View File

@@ -21,8 +21,8 @@ import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.digest.CachingAuthenticator; import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials; import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator; import com.burgstaller.okhttp.digest.DigestAuthenticator;
import com.m2049r.levin.scanner.Dispatcher;
import com.m2049r.levin.scanner.LevinPeer; import com.m2049r.levin.scanner.LevinPeer;
import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.OkHttpHelper;
import org.json.JSONException; import org.json.JSONException;
@@ -36,6 +36,8 @@ import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
import lombok.Setter;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@@ -46,14 +48,24 @@ import okhttp3.ResponseBody;
import timber.log.Timber; import timber.log.Timber;
public class NodeInfo extends Node { public class NodeInfo extends Node {
final static public int MIN_MAJOR_VERSION = 11; final static public int MIN_MAJOR_VERSION = 14;
final static public String RPC_VERSION = "2.0"; final static public String RPC_VERSION = "2.0";
@Getter
private long height = 0; private long height = 0;
@Getter
private long timestamp = 0; private long timestamp = 0;
@Getter
private int majorVersion = 0; private int majorVersion = 0;
@Getter
private double responseTime = Double.MAX_VALUE; private double responseTime = Double.MAX_VALUE;
@Getter
private int responseCode = 0; private int responseCode = 0;
@Getter
private boolean tested = false;
@Getter
@Setter
private boolean selecting = false;
public void clear() { public void clear() {
height = 0; height = 0;
@@ -61,13 +73,13 @@ public class NodeInfo extends Node {
responseTime = Double.MAX_VALUE; responseTime = Double.MAX_VALUE;
responseCode = 0; responseCode = 0;
timestamp = 0; timestamp = 0;
tested = false;
} }
static public NodeInfo fromString(String nodeString) { static public NodeInfo fromString(String nodeString) {
try { try {
return new NodeInfo(nodeString); return new NodeInfo(nodeString);
} catch (IllegalArgumentException ex) { } catch (IllegalArgumentException ex) {
Timber.w(ex);
return null; return null;
} }
} }
@@ -113,26 +125,6 @@ public class NodeInfo extends Node {
super(); super();
} }
public long getHeight() {
return height;
}
public long getTimestamp() {
return timestamp;
}
public int getMajorVersion() {
return majorVersion;
}
public double getResponseTime() {
return responseTime;
}
public int getResponseCode() {
return responseCode;
}
public boolean isSuccessful() { public boolean isSuccessful() {
return (responseCode >= 200) && (responseCode < 300); return (responseCode >= 200) && (responseCode < 300);
} }
@@ -145,23 +137,20 @@ public class NodeInfo extends Node {
return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE); return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE);
} }
static public Comparator<NodeInfo> BestNodeComparator = new Comparator<NodeInfo>() { static public Comparator<NodeInfo> BestNodeComparator = (o1, o2) -> {
@Override if (o1.isValid()) {
public int compare(NodeInfo o1, NodeInfo o2) { if (o2.isValid()) { // both are valid
if (o1.isValid()) { // higher node wins
if (o2.isValid()) { // both are valid int heightDiff = (int) (o2.height - o1.height);
// higher node wins if (heightDiff != 0)
int heightDiff = (int) (o2.height - o1.height); return heightDiff;
if (Math.abs(heightDiff) > Dispatcher.HEIGHT_WINDOW) // if they are equal, faster node wins
return heightDiff; return (int) Math.signum(o1.responseTime - o2.responseTime);
// if they are (nearly) equal, faster node wins
return (int) Math.signum(o1.responseTime - o2.responseTime);
} else {
return -1;
}
} else { } else {
return 1; return -1;
} }
} else {
return 1;
} }
}; };
@@ -192,7 +181,7 @@ public class NodeInfo extends Node {
} }
private static final int HTTP_TIMEOUT = OkHttpHelper.HTTP_TIMEOUT; private static final int HTTP_TIMEOUT = OkHttpHelper.HTTP_TIMEOUT;
public static final double PING_GOOD = HTTP_TIMEOUT / 3; //ms public static final double PING_GOOD = HTTP_TIMEOUT / 3.0; //ms
public static final double PING_MEDIUM = 2 * PING_GOOD; //ms public static final double PING_MEDIUM = 2 * PING_GOOD; //ms
public static final double PING_BAD = HTTP_TIMEOUT; public static final double PING_BAD = HTTP_TIMEOUT;
@@ -200,7 +189,15 @@ public class NodeInfo extends Node {
return testRpcService(rpcPort); return testRpcService(rpcPort);
} }
public boolean testRpcService(NodePinger.Listener listener) {
boolean result = testRpcService(rpcPort);
if (listener != null)
listener.publish(this);
return result;
}
private boolean testRpcService(int port) { private boolean testRpcService(int port) {
Timber.d("Testing %s", toNodeString());
clear(); clear();
try { try {
OkHttpClient client = OkHttpHelper.getEagerClient(); OkHttpClient client = OkHttpHelper.getEagerClient();
@@ -247,8 +244,9 @@ public class NodeInfo extends Node {
} }
} }
} catch (IOException | JSONException ex) { } catch (IOException | JSONException ex) {
// failure Timber.d(ex);
Timber.d(ex.getMessage()); } finally {
tested = true;
} }
return false; return false;
} }

View File

@@ -20,6 +20,7 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet;
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents // https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
public class TxData implements Parcelable { public class TxData implements Parcelable {
@@ -52,6 +53,10 @@ public class TxData implements Parcelable {
return amount; return amount;
} }
public double getAmountAsDouble() {
return 1.0 * amount / 1000000000000L;
}
public int getMixin() { public int getMixin() {
return mixin; return mixin;
} }
@@ -68,6 +73,10 @@ public class TxData implements Parcelable {
this.amount = amount; this.amount = amount;
} }
public void setAmount(double amount) {
this.amount = Wallet.getAmountFromDouble(amount);
}
public void setMixin(int mixin) { public void setMixin(int mixin) {
this.mixin = mixin; this.mixin = mixin;
} }

View File

@@ -18,11 +18,23 @@ package com.m2049r.xmrwallet.data;
import android.os.Parcel; import android.os.Parcel;
public class TxDataBtc extends TxData { import androidx.annotation.NonNull;
private String xmrtoUuid; import lombok.Getter;
import lombok.Setter;
public class TxDataBtc extends TxData {
@Getter
@Setter
private String btcSymbol; // the actual non-XMR thing we're sending
@Getter
@Setter
private String xmrtoOrderId; // shown in success screen
@Getter
@Setter
private String btcAddress; private String btcAddress;
private String bip70; @Getter
@Setter
private double btcAmount; private double btcAmount;
public TxDataBtc() { public TxDataBtc() {
@@ -33,44 +45,12 @@ public class TxDataBtc extends TxData {
super(txDataBtc); super(txDataBtc);
} }
public String getXmrtoUuid() {
return xmrtoUuid;
}
public void setXmrtoUuid(String xmrtoUuid) {
this.xmrtoUuid = xmrtoUuid;
}
public String getBtcAddress() {
return btcAddress;
}
public void setBtcAddress(String btcAddress) {
this.btcAddress = btcAddress;
}
public String getBip70() {
return bip70;
}
public void setBip70(String bip70) {
this.bip70 = bip70;
}
public double getBtcAmount() {
return btcAmount;
}
public void setBtcAmount(double btcAmount) {
this.btcAmount = btcAmount;
}
@Override @Override
public void writeToParcel(Parcel out, int flags) { public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags); super.writeToParcel(out, flags);
out.writeString(xmrtoUuid); out.writeString(btcSymbol);
out.writeString(xmrtoOrderId);
out.writeString(btcAddress); out.writeString(btcAddress);
out.writeString(bip70);
out.writeDouble(btcAmount); out.writeDouble(btcAmount);
} }
@@ -87,23 +67,35 @@ public class TxDataBtc extends TxData {
protected TxDataBtc(Parcel in) { protected TxDataBtc(Parcel in) {
super(in); super(in);
xmrtoUuid = in.readString(); btcSymbol = in.readString();
xmrtoOrderId = in.readString();
btcAddress = in.readString(); btcAddress = in.readString();
bip70 = in.readString();
btcAmount = in.readDouble(); btcAmount = in.readDouble();
} }
@NonNull
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(",xmrtoUuid:"); sb.append("xmrtoOrderId:");
sb.append(xmrtoUuid); sb.append(xmrtoOrderId);
sb.append(",btcSymbol:");
sb.append(btcSymbol);
sb.append(",btcAddress:"); sb.append(",btcAddress:");
sb.append(btcAddress); sb.append(btcAddress);
sb.append(",bip70:");
sb.append(bip70);
sb.append(",btcAmount:"); sb.append(",btcAmount:");
sb.append(btcAmount); sb.append(btcAmount);
return sb.toString(); return sb.toString();
} }
public boolean validateAddress(@NonNull String address) {
if ((btcSymbol == null) || (btcAddress == null)) return false;
final Crypto crypto = Crypto.withSymbol(btcSymbol);
if (crypto == null) return false;
if (crypto.isCasefull()) { // compare as-is
return address.equals(btcAddress);
} else { // normalize & compare (e.g. ETH with and without checksum capitals
return address.toLowerCase().equals(btcAddress.toLowerCase());
}
}
} }

View File

@@ -16,18 +16,19 @@
package com.m2049r.xmrwallet.data; package com.m2049r.xmrwallet.data;
import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus; import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder;
import com.m2049r.xmrwallet.util.Helper;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import timber.log.Timber;
public class UserNotes { public class UserNotes {
public String txNotes = ""; public String txNotes = "";
public String note = ""; public String note = "";
public String xmrtoTag = null;
public String xmrtoKey = null; public String xmrtoKey = null;
public String xmrtoAmount = null; // could be a double - but we are not doing any calculations public String xmrtoAmount = null; // could be a double - but we are not doing any calculations
public String xmrtoCurrency = null;
public String xmrtoDestination = null; public String xmrtoDestination = null;
public UserNotes(final String txNotes) { public UserNotes(final String txNotes) {
@@ -35,13 +36,15 @@ public class UserNotes {
return; return;
} }
this.txNotes = txNotes; this.txNotes = txNotes;
Pattern p = Pattern.compile("^\\{(xmrto-\\w{6}),([0-9.]*)BTC,(\\w*)\\} ?(.*)"); Pattern p = Pattern.compile("^\\{([a-z]+)-(\\w{6,}),([0-9.]*)([A-Z]+),(\\w*)\\} ?(.*)");
Matcher m = p.matcher(txNotes); Matcher m = p.matcher(txNotes);
if (m.find()) { if (m.find()) {
xmrtoKey = m.group(1); xmrtoTag = m.group(1);
xmrtoAmount = m.group(2); xmrtoKey = m.group(2);
xmrtoDestination = m.group(3); xmrtoAmount = m.group(3);
note = m.group(4); xmrtoCurrency = m.group(4);
xmrtoDestination = m.group(5);
note = m.group(6);
} else { } else {
note = txNotes; note = txNotes;
} }
@@ -56,12 +59,15 @@ public class UserNotes {
txNotes = buildTxNote(); txNotes = buildTxNote();
} }
public void setXmrtoStatus(QueryOrderStatus xmrtoStatus) { public void setXmrtoOrder(CreateOrder order) {
if (xmrtoStatus != null) { if (order != null) {
xmrtoKey = xmrtoStatus.getUuid(); xmrtoTag = order.TAG;
xmrtoAmount = String.valueOf(xmrtoStatus.getBtcAmount()); xmrtoKey = order.getOrderId();
xmrtoDestination = xmrtoStatus.getBtcDestAddress(); xmrtoAmount = Helper.getDisplayAmount(order.getBtcAmount());
xmrtoCurrency = order.getBtcCurrency();
xmrtoDestination = order.getBtcAddress();
} else { } else {
xmrtoTag = null;
xmrtoKey = null; xmrtoKey = null;
xmrtoAmount = null; xmrtoAmount = null;
xmrtoDestination = null; xmrtoDestination = null;
@@ -70,15 +76,18 @@ public class UserNotes {
} }
private String buildTxNote() { private String buildTxNote() {
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
if (xmrtoKey != null) { if (xmrtoKey != null) {
if ((xmrtoAmount == null) || (xmrtoDestination == null)) if ((xmrtoAmount == null) || (xmrtoDestination == null))
throw new IllegalArgumentException("Broken notes"); throw new IllegalArgumentException("Broken notes");
sb.append("{"); sb.append("{");
sb.append(xmrtoTag);
sb.append("-");
sb.append(xmrtoKey); sb.append(xmrtoKey);
sb.append(","); sb.append(",");
sb.append(xmrtoAmount); sb.append(xmrtoAmount);
sb.append("BTC,"); sb.append(xmrtoCurrency);
sb.append(",");
sb.append(xmrtoDestination); sb.append(xmrtoDestination);
sb.append("}"); sb.append("}");
if ((note != null) && (!note.isEmpty())) if ((note != null) && (!note.isEmpty()))

View File

@@ -27,7 +27,6 @@ import android.widget.TextView;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;

View File

@@ -42,9 +42,8 @@ public class SendAmountWizardFragment extends SendWizardFragment {
Listener sendListener; Listener sendListener;
public SendAmountWizardFragment setSendListener(Listener listener) { public void setSendListener(Listener listener) {
this.sendListener = listener; this.sendListener = listener;
return this;
} }
interface Listener { interface Listener {

View File

@@ -16,6 +16,9 @@
package com.m2049r.xmrwallet.fragment.send; package com.m2049r.xmrwallet.fragment.send;
import android.content.Intent;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -27,15 +30,17 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.data.PendingTx; import com.m2049r.xmrwallet.data.PendingTx;
import com.m2049r.xmrwallet.data.TxDataBtc; import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.service.shift.ShiftCallback;
import com.m2049r.xmrwallet.service.shift.ShiftException;
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.xmrto.XmrToException; import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
import com.m2049r.xmrwallet.xmrto.api.XmrToApi;
import com.m2049r.xmrwallet.xmrto.api.XmrToCallback;
import com.m2049r.xmrwallet.xmrto.network.XmrToApiImpl;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Locale; import java.util.Locale;
@@ -52,23 +57,23 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
SendSuccessWizardFragment.Listener sendListener; SendSuccessWizardFragment.Listener sendListener;
public SendBtcSuccessWizardFragment setSendListener(SendSuccessWizardFragment.Listener listener) { public void setSendListener(SendSuccessWizardFragment.Listener listener) {
this.sendListener = listener; this.sendListener = listener;
return this;
} }
ImageButton bCopyTxId; ImageButton bCopyTxId;
private TextView tvTxId; private TextView tvTxId;
private TextView tvTxAddress; private TextView tvTxAddress;
private TextView tvTxPaymentId;
private TextView tvTxAmount; private TextView tvTxAmount;
private TextView tvTxFee; private TextView tvTxFee;
private TextView tvXmrToAmount; private TextView tvXmrToAmount;
private ImageView ivXmrToIcon;
private TextView tvXmrToStatus; private TextView tvXmrToStatus;
private ImageView ivXmrToStatus; private ImageView ivXmrToStatus;
private ImageView ivXmrToStatusBig; private ImageView ivXmrToStatusBig;
private ProgressBar pbXmrto; private ProgressBar pbXmrto;
private TextView tvTxXmrToKey; private TextView tvTxXmrToKey;
private TextView tvXmrToSupport;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -87,13 +92,13 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
}); });
tvXmrToAmount = view.findViewById(R.id.tvXmrToAmount); tvXmrToAmount = view.findViewById(R.id.tvXmrToAmount);
ivXmrToIcon = view.findViewById(R.id.ivXmrToIcon);
tvXmrToStatus = view.findViewById(R.id.tvXmrToStatus); tvXmrToStatus = view.findViewById(R.id.tvXmrToStatus);
ivXmrToStatus = view.findViewById(R.id.ivXmrToStatus); ivXmrToStatus = view.findViewById(R.id.ivXmrToStatus);
ivXmrToStatusBig = view.findViewById(R.id.ivXmrToStatusBig); ivXmrToStatusBig = view.findViewById(R.id.ivXmrToStatusBig);
tvTxId = view.findViewById(R.id.tvTxId); tvTxId = view.findViewById(R.id.tvTxId);
tvTxAddress = view.findViewById(R.id.tvTxAddress); tvTxAddress = view.findViewById(R.id.tvTxAddress);
tvTxPaymentId = view.findViewById(R.id.tvTxPaymentId);
tvTxAmount = view.findViewById(R.id.tvTxAmount); tvTxAmount = view.findViewById(R.id.tvTxAmount);
tvTxFee = view.findViewById(R.id.tvTxFee); tvTxFee = view.findViewById(R.id.tvTxFee);
@@ -101,14 +106,14 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
pbXmrto.getIndeterminateDrawable().setColorFilter(0x61000000, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(0x61000000, android.graphics.PorterDuff.Mode.MULTIPLY);
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey); tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() { tvTxXmrToKey.setOnClickListener(v -> {
@Override Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
public void onClick(View v) { Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
}
}); });
tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport);
tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
return view; return view;
} }
@@ -147,9 +152,16 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
NumberFormat df = NumberFormat.getInstance(Locale.US); NumberFormat df = NumberFormat.getInstance(Locale.US);
df.setMaximumFractionDigits(12); df.setMaximumFractionDigits(12);
String btcAmount = df.format(btcData.getBtcAmount()); String btcAmount = df.format(btcData.getBtcAmount());
tvXmrToAmount.setText(getString(R.string.info_send_xmrto_success_btc, btcAmount)); tvXmrToAmount.setText(getString(R.string.info_send_xmrto_success_btc, btcAmount, btcData.getBtcSymbol()));
//TODO btcData.getBtcAddress(); //TODO btcData.getBtcAddress();
tvTxXmrToKey.setText(btcData.getXmrtoUuid()); tvTxXmrToKey.setText(btcData.getXmrtoOrderId());
final Crypto crypto = Crypto.withSymbol(btcData.getBtcSymbol());
ivXmrToIcon.setImageResource(crypto.getIconEnabledId());
tvXmrToSupport.setOnClickListener(v -> {
Uri orderUri = getXmrToApi().getQueryOrderUri(btcData.getXmrtoOrderId());
Intent intent = new Intent(Intent.ACTION_VIEW, orderUri);
startActivity(intent);
});
queryOrder(); queryOrder();
} else { } else {
throw new IllegalStateException("btcData is null"); throw new IllegalStateException("btcData is null");
@@ -158,33 +170,23 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
sendListener.enableDone(); sendListener.enableDone();
} }
private final int QUERY_INTERVAL = 1000; // ms
private void processQueryOrder(final QueryOrderStatus status) { private void processQueryOrder(final QueryOrderStatus status) {
Timber.d("processQueryOrder %s for %s", status.getState().toString(), status.getUuid()); Timber.d("processQueryOrder %s for %s", status.getState().toString(), status.getOrderId());
if (!btcData.getXmrtoUuid().equals(status.getUuid())) if (!btcData.getXmrtoOrderId().equals(status.getOrderId()))
throw new IllegalStateException("UUIDs do not match!"); throw new IllegalStateException("UUIDs do not match!");
if (isResumed && (getView() != null)) if (isResumed && (getView() != null))
getView().post(new Runnable() { getView().post(() -> {
@Override showXmrToStatus(status);
public void run() { if (!status.isTerminal()) {
showXmrToStatus(status); getView().postDelayed(this::queryOrder, SideShiftApi.QUERY_INTERVAL);
if (!status.isTerminal()) {
getView().postDelayed(new Runnable() {
@Override
public void run() {
queryOrder();
}
}, QUERY_INTERVAL);
}
} }
}); });
} }
private void queryOrder() { private void queryOrder() {
Timber.d("queryOrder(%s)", btcData.getXmrtoUuid()); Timber.d("queryOrder(%s)", btcData.getXmrtoOrderId());
if (!isResumed) return; if (!isResumed) return;
getXmrToApi().queryOrderStatus(btcData.getXmrtoUuid(), new XmrToCallback<QueryOrderStatus>() { getXmrToApi().queryOrderStatus(btcData.getXmrtoOrderId(), new ShiftCallback<QueryOrderStatus>() {
@Override @Override
public void onSuccess(QueryOrderStatus status) { public void onSuccess(QueryOrderStatus status) {
if (!isAdded()) return; if (!isAdded()) return;
@@ -194,38 +196,34 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
@Override @Override
public void onError(final Exception ex) { public void onError(final Exception ex) {
if (!isResumed) return; if (!isResumed) return;
Timber.e(ex); Timber.w(ex);
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(() -> {
@Override if (ex instanceof ShiftException) {
public void run() { Toast.makeText(getActivity(), ((ShiftException) ex).getError().getErrorMsg(), Toast.LENGTH_LONG).show();
if (ex instanceof XmrToException) { } else {
Toast.makeText(getActivity(), ((XmrToException) ex).getError().getErrorMsg(), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), ex.getLocalizedMessage(), Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), ex.getLocalizedMessage(), Toast.LENGTH_LONG).show();
}
} }
}); });
} }
}); });
} }
private int statusResource = 0;
void showXmrToStatus(final QueryOrderStatus status) { void showXmrToStatus(final QueryOrderStatus status) {
int statusResource = 0;
if (status.isError()) { if (status.isError()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_error, status.toString())); tvXmrToStatus.setText(getString(R.string.info_send_xmrto_error, status.toString()));
statusResource = R.drawable.ic_error_red_24dp; statusResource = R.drawable.ic_error_red_24dp;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xff8b0000, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(0xff8b0000, android.graphics.PorterDuff.Mode.MULTIPLY);
} else if (status.isSent()) { } else if (status.isSent() || status.isPaid()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_sent)); tvXmrToStatus.setText(getString(R.string.info_send_xmrto_sent, btcData.getBtcSymbol()));
statusResource = R.drawable.ic_success_green_24dp; statusResource = R.drawable.ic_success_green_24dp;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xFF417505, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(0xFF417505, android.graphics.PorterDuff.Mode.MULTIPLY);
} else if (status.isWaiting()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_unpaid));
statusResource = R.drawable.ic_pending_orange_24dp;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xFFFF6105, android.graphics.PorterDuff.Mode.MULTIPLY);
} else if (status.isPending()) { } else if (status.isPending()) {
if (status.isPaid()) { tvXmrToStatus.setText(getString(R.string.info_send_xmrto_paid));
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_paid));
} else {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_unpaid));
}
statusResource = R.drawable.ic_pending_orange_24dp; statusResource = R.drawable.ic_pending_orange_24dp;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xFFFF6105, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(0xFFFF6105, android.graphics.PorterDuff.Mode.MULTIPLY);
} else { } else {
@@ -234,20 +232,21 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
ivXmrToStatus.setImageResource(statusResource); ivXmrToStatus.setImageResource(statusResource);
if (status.isTerminal()) { if (status.isTerminal()) {
pbXmrto.setVisibility(View.INVISIBLE); pbXmrto.setVisibility(View.INVISIBLE);
ivXmrToIcon.setVisibility(View.GONE);
ivXmrToStatus.setVisibility(View.GONE); ivXmrToStatus.setVisibility(View.GONE);
ivXmrToStatusBig.setImageResource(statusResource); ivXmrToStatusBig.setImageResource(statusResource);
ivXmrToStatusBig.setVisibility(View.VISIBLE); ivXmrToStatusBig.setVisibility(View.VISIBLE);
} }
} }
private XmrToApi xmrToApi = null; private SideShiftApi xmrToApi = null;
private final XmrToApi getXmrToApi() { private SideShiftApi getXmrToApi() {
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new XmrToApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(),
Helper.getXmrToBaseUrl()); ServiceHelper.getXmrToBaseUrl());
} }
} }
} }

View File

@@ -29,6 +29,7 @@ import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@@ -92,8 +93,6 @@ public class SendFragment extends Fragment
void setOnUriScannedListener(OnUriScannedListener onUriScannedListener); void setOnUriScannedListener(OnUriScannedListener onUriScannedListener);
} }
private EditText etDummy;
private View llNavBar; private View llNavBar;
private DotBar dotBar; private DotBar dotBar;
private Button bPrev; private Button bPrev;
@@ -101,7 +100,7 @@ public class SendFragment extends Fragment
private Button bDone; private Button bDone;
static private int MAX_FALLBACK = Integer.MAX_VALUE; static private final int MAX_FALLBACK = Integer.MAX_VALUE;
public static SendFragment newInstance(String uri) { public static SendFragment newInstance(String uri) {
SendFragment f = new SendFragment(); SendFragment f = new SendFragment();
@@ -166,28 +165,18 @@ public class SendFragment extends Fragment
} }
}); });
bPrev.setOnClickListener(new View.OnClickListener() { bPrev.setOnClickListener(v -> spendViewPager.previous());
public void onClick(View v) {
spendViewPager.previous();
}
});
bNext.setOnClickListener(new View.OnClickListener() { bNext.setOnClickListener(v -> spendViewPager.next());
public void onClick(View v) {
spendViewPager.next();
}
});
bDone.setOnClickListener(new View.OnClickListener() { bDone.setOnClickListener(v -> {
public void onClick(View v) { Timber.d("bDone.onClick");
Timber.d("bDone.onClick"); activityCallback.onFragmentDone();
activityCallback.onFragmentDone();
}
}); });
updatePosition(0); updatePosition(0);
etDummy = view.findViewById(R.id.etDummy); final EditText etDummy = view.findViewById(R.id.etDummy);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etDummy.requestFocus(); etDummy.requestFocus();
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@@ -197,7 +186,7 @@ public class SendFragment extends Fragment
String uri = args.getString(WalletActivity.REQUEST_URI); String uri = args.getString(WalletActivity.REQUEST_URI);
Timber.d("URI: %s", uri); Timber.d("URI: %s", uri);
if (uri != null) { if (uri != null) {
barcodeData = BarcodeData.fromQrCode(uri); barcodeData = BarcodeData.fromString(uri);
Timber.d("barcodeData: %s", barcodeData != null ? barcodeData.toString() : "null"); Timber.d("barcodeData: %s", barcodeData != null ? barcodeData.toString() : "null");
} }
} }
@@ -236,7 +225,7 @@ public class SendFragment extends Fragment
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
Timber.d("onAttach %s", context); Timber.d("onAttach %s", context);
super.onAttach(context); super.onAttach(context);
if (context instanceof Listener) { if (context instanceof Listener) {
@@ -300,12 +289,7 @@ public class SendFragment extends Fragment
default: default:
throw new IllegalArgumentException("Mode " + String.valueOf(aMode) + " unknown!"); throw new IllegalArgumentException("Mode " + String.valueOf(aMode) + " unknown!");
} }
getView().post(new Runnable() { getView().post(() -> pagerAdapter.notifyDataSetChanged());
@Override
public void run() {
pagerAdapter.notifyDataSetChanged();
}
});
Timber.d("New Mode = %s", mode.toString()); Timber.d("New Mode = %s", mode.toString());
} }
} }
@@ -338,8 +322,9 @@ public class SendFragment extends Fragment
return numPages; return numPages;
} }
@NonNull
@Override @Override
public Object instantiateItem(ViewGroup container, int position) { public Object instantiateItem(@NonNull ViewGroup container, int position) {
Timber.d("instantiateItem %d", position); Timber.d("instantiateItem %d", position);
SendWizardFragment fragment = (SendWizardFragment) super.instantiateItem(container, position); SendWizardFragment fragment = (SendWizardFragment) super.instantiateItem(container, position);
myFragments.put(position, new WeakReference<>(fragment)); myFragments.put(position, new WeakReference<>(fragment));
@@ -347,20 +332,21 @@ public class SendFragment extends Fragment
} }
@Override @Override
public void destroyItem(ViewGroup container, int position, Object object) { public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Timber.d("destroyItem %d", position); Timber.d("destroyItem %d", position);
myFragments.remove(position); myFragments.remove(position);
super.destroyItem(container, position, object); super.destroyItem(container, position, object);
} }
public SendWizardFragment getFragment(int position) { public SendWizardFragment getFragment(int position) {
WeakReference ref = myFragments.get(position); WeakReference<SendWizardFragment> ref = myFragments.get(position);
if (ref != null) if (ref != null)
return myFragments.get(position).get(); return myFragments.get(position).get();
else else
return null; return null;
} }
@NonNull
@Override @Override
public SendWizardFragment getItem(int position) { public SendWizardFragment getItem(int position) {
Timber.d("getItem(%d) CREATE", position); Timber.d("getItem(%d) CREATE", position);
@@ -415,7 +401,7 @@ public class SendFragment extends Fragment
} }
@Override @Override
public int getItemPosition(Object object) { public int getItemPosition(@NonNull Object object) {
Timber.d("getItemPosition %s", String.valueOf(object)); Timber.d("getItemPosition %s", String.valueOf(object));
if (object instanceof SendAddressWizardFragment) { if (object instanceof SendAddressWizardFragment) {
// keep these pages // keep these pages
@@ -563,22 +549,4 @@ public class SendFragment extends Fragment
inflater.inflate(R.menu.send_menu, menu); inflater.inflate(R.menu.send_menu, menu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }
// xmr.to info box
private static final String PREF_SHOW_XMRTO_ENABLED = "info_xmrto_enabled_send";
boolean showXmrtoEnabled = true;
void loadPrefs() {
SharedPreferences sharedPref = activityCallback.getPrefs();
showXmrtoEnabled = sharedPref.getBoolean(PREF_SHOW_XMRTO_ENABLED, true);
}
void saveXmrToPrefs() {
SharedPreferences sharedPref = activityCallback.getPrefs();
SharedPreferences.Editor editor = sharedPref.edit();
editor.putBoolean(PREF_SHOW_XMRTO_ENABLED, showXmrtoEnabled);
editor.apply();
}
} }

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.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -26,8 +24,13 @@ import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
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.Helper;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -35,7 +38,6 @@ import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.TimeZone; import java.util.TimeZone;
@@ -44,6 +46,8 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
public interface OnInteractionListener { public interface OnInteractionListener {
void onInteraction(View view, NodeInfo item); void onInteraction(View view, NodeInfo item);
void onLongInteraction(View view, NodeInfo item);
} }
private final List<NodeInfo> nodeItems = new ArrayList<>(); private final List<NodeInfo> nodeItems = new ArrayList<>();
@@ -106,8 +110,9 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
notifyDataSetChanged(); notifyDataSetChanged();
} }
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
final ImageButton ibBookmark; final ImageButton ibBookmark;
final View pbBookmark;
final TextView tvName; final TextView tvName;
final TextView tvIp; final TextView tvIp;
final ImageView ivPing; final ImageView ivPing;
@@ -116,16 +121,20 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
ViewHolder(View itemView) { ViewHolder(View itemView) {
super(itemView); super(itemView);
ibBookmark = itemView.findViewById(R.id.ibBookmark); ibBookmark = itemView.findViewById(R.id.ibBookmark);
pbBookmark = itemView.findViewById(R.id.pbBookmark);
tvName = itemView.findViewById(R.id.tvName); tvName = itemView.findViewById(R.id.tvName);
tvIp = itemView.findViewById(R.id.tvAddress); tvIp = itemView.findViewById(R.id.tvAddress);
ivPing = itemView.findViewById(R.id.ivPing); ivPing = itemView.findViewById(R.id.ivPing);
ibBookmark.setOnClickListener(new View.OnClickListener() { ibBookmark.setOnClickListener(v -> {
@Override nodeItem.toggleFavourite();
public void onClick(View v) { showStar();
nodeItem.toggleFavourite(); if (!nodeItem.isFavourite()) {
showStar(); nodeItem.setSelected(false);
notifyDataSetChanged();
} }
}); });
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
} }
private void showStar() { private void showStar() {
@@ -139,16 +148,22 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
void bind(int position) { void bind(int position) {
nodeItem = nodeItems.get(position); nodeItem = nodeItems.get(position);
tvName.setText(nodeItem.getName()); tvName.setText(nodeItem.getName());
final String ts = TS_FORMATTER.format(new Date(nodeItem.getTimestamp() * 1000));
ivPing.setImageResource(getPingIcon(nodeItem)); ivPing.setImageResource(getPingIcon(nodeItem));
if (nodeItem.isValid()) { if (nodeItem.isTested()) {
tvIp.setText(context.getString(R.string.node_height, ts)); if (nodeItem.isValid()) {
Helper.showTimeDifference(tvIp, nodeItem.getTimestamp());
} else {
tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode()));
tvIp.setTextColor(ColorHelper.getThemedColor(tvIp.getContext(), R.attr.colorError));
}
} else { } else {
tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode())); tvIp.setText(context.getResources().getString(R.string.node_testing, nodeItem.getHostAddress()));
} }
itemView.setOnClickListener(this); itemView.setSelected(nodeItem.isSelected());
itemView.setClickable(itemsClickable); itemView.setClickable(itemsClickable);
itemView.setEnabled(itemsClickable);
ibBookmark.setClickable(itemsClickable); ibBookmark.setClickable(itemsClickable);
pbBookmark.setVisibility(nodeItem.isSelecting() ? View.VISIBLE : View.INVISIBLE);
showStar(); showStar();
} }
@@ -157,10 +172,24 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
if (listener != null) { if (listener != null) {
int position = getAdapterPosition(); // gets item position int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
listener.onInteraction(view, nodeItems.get(position)); final NodeInfo node = nodeItems.get(position);
node.setSelecting(true);
allowClick(false);
listener.onInteraction(view, node);
} }
} }
} }
@Override
public boolean onLongClick(View view) {
if (listener != null) {
int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
listener.onLongInteraction(view, nodeItems.get(position));
}
}
return true;
}
} }
static public int getPingIcon(NodeInfo nodeInfo) { static public int getPingIcon(NodeInfo nodeInfo) {

View File

@@ -17,18 +17,20 @@
package com.m2049r.xmrwallet.layout; package com.m2049r.xmrwallet.layout;
import android.content.Context; import android.content.Context;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.data.UserNotes;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
@@ -141,7 +143,13 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
UserNotes userNotes = new UserNotes(infoItem.notes); UserNotes userNotes = new UserNotes(infoItem.notes);
if (userNotes.xmrtoKey != null) { if (userNotes.xmrtoKey != null) {
ivTxType.setVisibility(View.VISIBLE); final Crypto crypto = Crypto.withSymbol(userNotes.xmrtoCurrency);
if (crypto != null) {
ivTxType.setImageResource(crypto.getIconEnabledId());
ivTxType.setVisibility(View.VISIBLE);
} else {// otherwirse pretend we don't know it's a shift
ivTxType.setVisibility(View.GONE);
}
} else { } else {
ivTxType.setVisibility(View.GONE); // gives us more space for the amount ivTxType.setVisibility(View.GONE); // gives us more space for the amount
} }

View File

@@ -2,7 +2,7 @@
******************************************************************************* *******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API * BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn * (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (c) m2049r * Copyright (c) 2018 m2049r
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

Some files were not shown because too many files have changed in this diff Show More