1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-06 19:00:30 +02:00

Compare commits

...

18 Commits

Author SHA1 Message Date
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
m2049r
6632547d1e bump version & fix versionCode (#694) 2020-10-19 23:44:40 +02:00
m2049r
f1b786ec3e fix keyboard crash when no view in focus (#693) 2020-10-19 23:42:22 +02:00
m2049r
a93e96d34c upgrade to monero v0.17.1.1 (#692) 2020-10-19 23:39:30 +02:00
m2049r
1829d30b61 Restyle UI (#690)
* enable testnet flavor
* v0.17.1.0
* migrate to androidx & material
* new color themes
* bump version v1.15.2
2020-10-16 02:52:08 +02:00
m2049r
e5b15b7816 always apply tx_amount from qr code (#687) 2020-09-27 11:00:57 +02:00
m2049r
7206857a5b bump version v1.14.8 2020-09-25 21:49:59 +02:00
m2049r
85d84d09ee fix crash when sending btc (#686) 2020-09-25 20:08:07 +02:00
315 changed files with 9470 additions and 5077 deletions

View File

@@ -1,15 +1,15 @@
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 407 versionCode 702
versionName "1.14.7 'On Board'" versionName "1.17.2 'Druk'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
cppFlags "-std=c++11" cppFlags "-std=c++11"
@@ -17,6 +17,11 @@ android {
} }
} }
} }
bundle {
language {
enableSplit = false
}
}
flavorDimensions 'type', 'net' flavorDimensions 'type', 'net'
productFlavors { productFlavors {
@@ -28,6 +33,11 @@ android {
applicationIdSuffix '.stage' applicationIdSuffix '.stage'
versionNameSuffix ' (stage)' versionNameSuffix ' (stage)'
} }
devnet {
dimension 'net'
applicationIdSuffix '.test'
versionNameSuffix ' (test)'
}
alpha { alpha {
dimension 'type' dimension 'type'
applicationIdSuffix '.alpha' applicationIdSuffix '.alpha'
@@ -46,6 +56,9 @@ android {
debug { debug {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
} }
applicationVariants.all { variant ->
variant.buildConfigField "String", "ID_A", "\"" + getId("ID_A") + "\""
}
} }
externalNativeBuild { externalNativeBuild {
@@ -70,7 +83,8 @@ android {
def availableLocales = ["en"] def availableLocales = ["en"]
new File("app/src/main/res/").eachFileMatch(~/^values-.*/) { file -> new File("app/src/main/res/").eachFileMatch(~/^values-.*/) { file ->
def languageTag = file.name.substring(7).replace("-r", "-") def languageTag = file.name.substring(7).replace("-r", "-")
availableLocales.add(languageTag) if (languageTag != "night")
availableLocales.add(languageTag)
} }
// APKs for the same app that all have the same version information. // APKs for the same app that all have the same version information.
@@ -81,8 +95,7 @@ android {
variant.outputs.all { variant.outputs.all {
output -> output ->
def abiName = output.getFilter(com.android.build.OutputFile.ABI) def abiName = output.getFilter(com.android.build.OutputFile.ABI)
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * versionCode
//def flavor = output.getFilter(flavor)
if (abiName == null) abiName = "universal" if (abiName == null) abiName = "universal"
def v = "${variant.versionName}".replaceFirst(" '.*' ?", "") def v = "${variant.versionName}".replaceFirst(" '.*' ?", "")
@@ -99,34 +112,42 @@ android {
} }
} }
def getId(name) {
def Properties props = new Properties()
props.load(new FileInputStream(new File('monerujo.id')))
return props[name]
}
dependencies { dependencies {
implementation "com.android.support:appcompat-v7:$rootProject.ext.supportVersion" implementation 'androidx.core:core:1.3.2'
implementation "com.android.support:design:$rootProject.ext.supportVersion" implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "com.android.support:support-v4:$rootProject.ext.supportVersion" implementation 'com.google.android.material:material:1.3.0'
implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion" implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion" implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation "com.android.support:swiperefreshlayout:$rootProject.ext.supportVersion" implementation 'androidx.cardview:cardview:1.0.0'
implementation "com.android.support.constraint:constraint-layout:$rootProject.ext.constraintVersion" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
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:4.9.0"
implementation "com.burgstaller:okhttp-digest:2.1"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" implementation 'com.nulab-inc:zxcvbn:1.3.0'
implementation "com.burgstaller:okhttp-digest:1.18"
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
implementation 'com.nulab-inc:zxcvbn:1.2.3' implementation 'dnsjava:dnsjava:2.1.9'
implementation 'org.jitsi:dnssecjava:1.2.0'
implementation 'dnsjava:dnsjava:2.1.8' implementation 'org.slf4j:slf4j-nop:1.7.30'
implementation 'org.jitsi:dnssecjava:1.1.3'
implementation 'org.slf4j:slf4j-nop:1.7.25'
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"
@@ -20,8 +21,9 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/MyMaterialTheme" android:theme="@style/MyMaterialTheme"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity android:name=".MainActivity" <activity
android:configChanges="orientation|keyboardHidden" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|uiMode"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait"> android:screenOrientation="portrait">
<intent-filter> <intent-filter>
@@ -31,13 +33,13 @@
</activity> </activity>
<activity <activity
android:name=".WalletActivity" android:name=".WalletActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden|uiMode"
android:label="@string/wallet_activity_name" android:label="@string/wallet_activity_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="behind" /> android:screenOrientation="behind"/>
<activity <activity
android:name=".LoginActivity" android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden|uiMode"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="locked"> android:screenOrientation="locked">
@@ -65,10 +67,11 @@
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" /> android:resource="@xml/usb_device_filter" />
</activity> </activity>
<activity android:name=".onboarding.OnBoardingActivity" <activity
android:configChanges="orientation|keyboardHidden" android:name=".onboarding.OnBoardingActivity"
android:configChanges="orientation|keyboardHidden|uiMode"
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="portrait"/> android:screenOrientation="portrait" />
<service <service
android:name=".service.WalletService" android:name=".service.WalletService"
@@ -77,7 +80,7 @@
android:label="Monero Wallet Service" /> android:label="Monero Wallet Service" />
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">

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;
@@ -15,9 +31,9 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.annotation.CallSuper; import androidx.annotation.CallSuper;
import android.support.annotation.Nullable; import androidx.annotation.Nullable;
import android.support.v4.app.Fragment; import androidx.fragment.app.Fragment;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;

View File

@@ -17,13 +17,9 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
@@ -38,9 +34,15 @@ import android.view.WindowManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.android.material.textfield.TextInputLayout;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.FingerprintHelper; import com.m2049r.xmrwallet.util.FingerprintHelper;
@@ -202,13 +204,13 @@ public class GenerateFragment extends Fragment {
if (FingerprintHelper.isDeviceSupported(getContext())) { if (FingerprintHelper.isDeviceSupported(getContext())) {
llFingerprintAuth.setVisibility(View.VISIBLE); llFingerprintAuth.setVisibility(View.VISIBLE);
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0); final SwitchMaterial swFingerprintAllowed = (SwitchMaterial) llFingerprintAuth.getChildAt(0);
swFingerprintAllowed.setOnClickListener(new View.OnClickListener() { swFingerprintAllowed.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { public void onClick(View view) {
if (!swFingerprintAllowed.isChecked()) return; if (!swFingerprintAllowed.isChecked()) return;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity());
builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn))) builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn)))
.setCancelable(false) .setCancelable(false)
.setPositiveButton(getString(R.string.label_ok), null) .setPositiveButton(getString(R.string.label_ok), null)
@@ -507,7 +509,7 @@ public class GenerateFragment extends Fragment {
String name = etWalletName.getEditText().getText().toString(); String name = etWalletName.getEditText().getText().toString();
String password = etWalletPassword.getEditText().getText().toString(); String password = etWalletPassword.getEditText().getText().toString();
boolean fingerprintAuthAllowed = ((Switch) llFingerprintAuth.getChildAt(0)).isChecked(); boolean fingerprintAuthAllowed = ((SwitchMaterial) llFingerprintAuth.getChildAt(0)).isChecked();
// create the real wallet password // create the real wallet password
String crazyPass = KeyStoreHelper.getCrazyPass(getActivity(), password); String crazyPass = KeyStoreHelper.getCrazyPass(getActivity(), password);
@@ -646,7 +648,7 @@ public class GenerateFragment extends Fragment {
if (ledgerDialog != null) return; if (ledgerDialog != null) return;
final Activity activity = getActivity(); final Activity activity = getActivity();
View promptsView = getLayoutInflater().inflate(R.layout.prompt_ledger_seed, null); View promptsView = getLayoutInflater().inflate(R.layout.prompt_ledger_seed, null);
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity); MaterialAlertDialogBuilder alertDialogBuilder = new MaterialAlertDialogBuilder(activity);
alertDialogBuilder.setView(promptsView); alertDialogBuilder.setView(promptsView);
final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed); final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed);

View File

@@ -16,14 +16,10 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.TextWatcher; import android.text.TextWatcher;
@@ -40,10 +36,17 @@ import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.Switch;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.switchmaterial.SwitchMaterial;
import com.google.android.material.textfield.TextInputLayout;
import com.m2049r.xmrwallet.ledger.Ledger; import com.m2049r.xmrwallet.ledger.Ledger;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.NetworkType;
@@ -118,31 +121,11 @@ public class GenerateReviewFragment extends Fragment {
tvWalletSpendKey.setTextIsSelectable(allowCopy); tvWalletSpendKey.setTextIsSelectable(allowCopy);
tvWalletPassword.setTextIsSelectable(allowCopy); tvWalletPassword.setTextIsSelectable(allowCopy);
bAccept.setOnClickListener(new View.OnClickListener() { bAccept.setOnClickListener(v -> acceptWallet());
@Override view.findViewById(R.id.bCopyViewKey).setOnClickListener(v -> copyViewKey());
public void onClick(View v) { bCopyAddress.setEnabled(false);
acceptWallet(); bCopyAddress.setOnClickListener(v -> copyAddress());
} view.findViewById(R.id.bAdvancedInfo).setOnClickListener(v -> showAdvancedInfo());
});
view.findViewById(R.id.bCopyViewKey).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyViewKey();
}
});
bCopyAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyAddress();
}
});
bCopyAddress.setClickable(false);
view.findViewById(R.id.bAdvancedInfo).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showAdvancedInfo();
}
});
Bundle args = getArguments(); Bundle args = getArguments();
type = args.getString(REQUEST_TYPE); type = args.getString(REQUEST_TYPE);
@@ -287,8 +270,7 @@ public class GenerateReviewFragment extends Fragment {
showAdvanced = true; showAdvanced = true;
} }
if (showAdvanced) bAdvancedInfo.setVisibility(View.VISIBLE); if (showAdvanced) bAdvancedInfo.setVisibility(View.VISIBLE);
bCopyAddress.setClickable(true); bCopyAddress.setEnabled(true);
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
activityCallback.setTitle(name, getString(R.string.details_title)); activityCallback.setTitle(name, getString(R.string.details_title));
activityCallback.setToolbarButton( activityCallback.setToolbarButton(
GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK); GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK);
@@ -352,7 +334,7 @@ public class GenerateReviewFragment extends Fragment {
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof Listener) { if (context instanceof Listener) {
this.activityCallback = (Listener) context; this.activityCallback = (Listener) context;
@@ -399,7 +381,7 @@ public class GenerateReviewFragment extends Fragment {
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
String type = getArguments().getString(REQUEST_TYPE); // intance variable <type> not set yet String type = getArguments().getString(REQUEST_TYPE); // intance variable <type> not set yet
if (GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type)) { if (GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type)) {
inflater.inflate(R.menu.wallet_details_help_menu, menu); inflater.inflate(R.menu.wallet_details_help_menu, menu);
@@ -448,7 +430,7 @@ public class GenerateReviewFragment extends Fragment {
protected Boolean doInBackground(String... params) { protected Boolean doInBackground(String... params) {
if (params.length != 2) return false; if (params.length != 2) return false;
final String userPassword = params[0]; final String userPassword = params[0];
final boolean fingerPassValid = Boolean.valueOf(params[1]); final boolean fingerPassValid = Boolean.parseBoolean(params[1]);
newPassword = KeyStoreHelper.getCrazyPass(getActivity(), userPassword); newPassword = KeyStoreHelper.getCrazyPass(getActivity(), userPassword);
final boolean success = changeWalletPassword(newPassword); final boolean success = changeWalletPassword(newPassword);
if (success) { if (success) {
@@ -488,7 +470,7 @@ public class GenerateReviewFragment extends Fragment {
LayoutInflater li = LayoutInflater.from(getActivity()); LayoutInflater li = LayoutInflater.from(getActivity());
View promptsView = li.inflate(R.layout.prompt_changepw, null); View promptsView = li.inflate(R.layout.prompt_changepw, null);
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(getActivity());
alertDialogBuilder.setView(promptsView); alertDialogBuilder.setView(promptsView);
final TextInputLayout etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA); final TextInputLayout etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA);
@@ -498,7 +480,7 @@ public class GenerateReviewFragment extends Fragment {
etPasswordB.setHint(getString(R.string.prompt_changepwB, walletName)); etPasswordB.setHint(getString(R.string.prompt_changepwB, walletName));
LinearLayout llFingerprintAuth = promptsView.findViewById(R.id.llFingerprintAuth); LinearLayout llFingerprintAuth = promptsView.findViewById(R.id.llFingerprintAuth);
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0); final SwitchMaterial swFingerprintAllowed = (SwitchMaterial) llFingerprintAuth.getChildAt(0);
if (FingerprintHelper.isDeviceSupported(getActivity())) { if (FingerprintHelper.isDeviceSupported(getActivity())) {
llFingerprintAuth.setVisibility(View.VISIBLE); llFingerprintAuth.setVisibility(View.VISIBLE);
@@ -507,7 +489,7 @@ public class GenerateReviewFragment extends Fragment {
public void onClick(View view) { public void onClick(View view) {
if (!swFingerprintAllowed.isChecked()) return; if (!swFingerprintAllowed.isChecked()) return;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new MaterialAlertDialogBuilder(getActivity());
builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn))) builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn)))
.setCancelable(false) .setCancelable(false)
.setPositiveButton(getString(R.string.label_ok), null) .setPositiveButton(getString(R.string.label_ok), null)
@@ -596,7 +578,7 @@ public class GenerateReviewFragment extends Fragment {
etPasswordA.setError(getString(R.string.generate_empty_passwordB)); etPasswordA.setError(getString(R.string.generate_empty_passwordB));
} else if (!newPasswordA.equals(newPasswordB)) { } else if (!newPasswordA.equals(newPasswordB)) {
etPasswordB.setError(getString(R.string.generate_bad_passwordB)); etPasswordB.setError(getString(R.string.generate_bad_passwordB));
} else if (newPasswordA.equals(newPasswordB)) { } else {
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked())); new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
Helper.hideKeyboardAlways(getActivity()); Helper.hideKeyboardAlways(getActivity());
openDialog.dismiss(); openDialog.dismiss();
@@ -619,7 +601,7 @@ public class GenerateReviewFragment extends Fragment {
etPasswordA.setError(getString(R.string.generate_empty_passwordB)); etPasswordA.setError(getString(R.string.generate_empty_passwordB));
} else if (!newPasswordA.equals(newPasswordB)) { } else if (!newPasswordA.equals(newPasswordB)) {
etPasswordB.setError(getString(R.string.generate_bad_passwordB)); etPasswordB.setError(getString(R.string.generate_bad_passwordB));
} else if (newPasswordA.equals(newPasswordB)) { } else {
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked())); new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
Helper.hideKeyboardAlways(getActivity()); Helper.hideKeyboardAlways(getActivity());
openDialog.dismiss(); openDialog.dismiss();

File diff suppressed because it is too large Load Diff

View File

@@ -19,11 +19,6 @@ package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@@ -38,12 +33,19 @@ import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.layout.NodeInfoAdapter; import com.m2049r.xmrwallet.layout.NodeInfoAdapter;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter; 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;
@@ -63,6 +65,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private List<WalletManager.WalletInfo> walletList = new ArrayList<>(); private List<WalletManager.WalletInfo> walletList = new ArrayList<>();
private List<WalletManager.WalletInfo> displayedList = new ArrayList<>(); private List<WalletManager.WalletInfo> displayedList = new ArrayList<>();
private View tvGuntherSays;
private ImageView ivGunther; private ImageView ivGunther;
private TextView tvNodeName; private TextView tvNodeName;
private TextView tvNodeAddress; private TextView tvNodeAddress;
@@ -103,6 +106,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Set<NodeInfo> getFavouriteNodes(); Set<NodeInfo> getFavouriteNodes();
Set<NodeInfo> getOrPopulateFavourites();
boolean hasLedger(); boolean hasLedger();
} }
@@ -130,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
@@ -143,6 +144,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Timber.d("onCreateView"); Timber.d("onCreateView");
View view = inflater.inflate(R.layout.fragment_login, container, false); View view = inflater.inflate(R.layout.fragment_login, container, false);
tvGuntherSays = view.findViewById(R.id.tvGuntherSays);
ivGunther = view.findViewById(R.id.ivGunther); ivGunther = view.findViewById(R.id.ivGunther);
fabScreen = view.findViewById(R.id.fabScreen); fabScreen = view.findViewById(R.id.fabScreen);
fab = view.findViewById(R.id.fab); fab = view.findViewById(R.id.fab);
@@ -183,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());
@@ -275,13 +264,15 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
if (displayedList.isEmpty()) { if (displayedList.isEmpty()) {
fab.startAnimation(fab_pulse); fab.startAnimation(fab_pulse);
if (ivGunther.getDrawable() == null) { if (ivGunther.getDrawable() == null) {
ivGunther.setImageResource(R.drawable.gunther_desaturated); ivGunther.setImageResource(R.drawable.ic_emptygunther);
tvGuntherSays.setVisibility(View.VISIBLE);
} }
} else { } else {
fab.clearAnimation(); fab.clearAnimation();
if (ivGunther.getDrawable() != null) { if (ivGunther.getDrawable() != null) {
ivGunther.setImageDrawable(null); ivGunther.setImageDrawable(null);
} }
tvGuntherSays.setVisibility(View.GONE);
} }
// remove information of non-existent wallet // remove information of non-existent wallet
@@ -416,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;
@@ -457,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);
}
} }
} }
@@ -480,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();
} }
} }

View File

@@ -18,13 +18,14 @@ package com.m2049r.xmrwallet;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.m2049r.xmrwallet.onboarding.OnBoardingActivity; import com.m2049r.xmrwallet.onboarding.OnBoardingActivity;
import com.m2049r.xmrwallet.onboarding.OnBoardingManager; import com.m2049r.xmrwallet.onboarding.OnBoardingManager;
public class MainActivity extends AppCompatActivity { public class MainActivity extends BaseActivity {
@Override @Override
protected void onCreate(@Nullable final Bundle savedInstanceState) { protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

File diff suppressed because it is too large Load Diff

View File

@@ -25,12 +25,6 @@ import android.net.Uri;
import android.nfc.NfcManager; import android.nfc.NfcManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.support.v4.content.FileProvider;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.ShareActionProvider;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
@@ -42,7 +36,6 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
@@ -50,12 +43,20 @@ import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.ShareActionProvider;
import androidx.core.content.FileProvider;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.Fragment;
import com.google.android.material.textfield.TextInputLayout;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType; import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException; 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.ledger.LedgerProgressDialog; import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
@@ -82,10 +83,11 @@ public class ReceiveFragment extends Fragment {
private ExchangeView evAmount; private ExchangeView evAmount;
private TextView tvQrCode; private TextView tvQrCode;
private ImageView ivQrCode; private ImageView ivQrCode;
private View cvQrCode;
private ImageView ivQrCodeFull; private ImageView ivQrCodeFull;
private EditText etDummy; private EditText etDummy;
private ImageButton bCopyAddress; private ImageButton bCopyAddress;
private Button bSubaddress; private ImageButton bSubaddress;
private Wallet wallet = null; private Wallet wallet = null;
private boolean isMyWallet = false; private boolean isMyWallet = false;
@@ -109,6 +111,7 @@ public class ReceiveFragment extends Fragment {
tvAddress = view.findViewById(R.id.tvAddress); tvAddress = view.findViewById(R.id.tvAddress);
etNotes = view.findViewById(R.id.etNotes); etNotes = view.findViewById(R.id.etNotes);
evAmount = view.findViewById(R.id.evAmount); evAmount = view.findViewById(R.id.evAmount);
cvQrCode = view.findViewById(R.id.cvQrCode);
ivQrCode = view.findViewById(R.id.qrCode); ivQrCode = view.findViewById(R.id.qrCode);
tvQrCode = view.findViewById(R.id.tvQrCode); tvQrCode = view.findViewById(R.id.tvQrCode);
ivQrCodeFull = view.findViewById(R.id.qrCodeFull); ivQrCodeFull = view.findViewById(R.id.qrCodeFull);
@@ -118,13 +121,9 @@ public class ReceiveFragment extends Fragment {
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
bCopyAddress.setOnClickListener(new View.OnClickListener() { bCopyAddress.setOnClickListener(v -> copyAddress());
@Override
public void onClick(View v) {
copyAddress();
}
});
enableCopyAddress(false); enableCopyAddress(false);
enableSubaddressButton(false);
evAmount.setOnNewAmountListener(new ExchangeView.OnNewAmountListener() { evAmount.setOnNewAmountListener(new ExchangeView.OnNewAmountListener() {
@Override @Override
@@ -190,7 +189,7 @@ public class ReceiveFragment extends Fragment {
} }
}); });
ivQrCode.setOnClickListener(new View.OnClickListener() { cvQrCode.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@@ -315,13 +314,8 @@ public class ReceiveFragment extends Fragment {
return null; return null;
} }
void enableSubaddressButton(boolean enable) { private void enableSubaddressButton(boolean enable) {
bSubaddress.setEnabled(enable); bSubaddress.setEnabled(enable);
if (enable) {
bSubaddress.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.ic_settings_orange_24dp, 0, 0);
} else {
bSubaddress.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.ic_settings_gray_24dp, 0, 0);
}
} }
void copyAddress() { void copyAddress() {
@@ -371,16 +365,13 @@ public class ReceiveFragment extends Fragment {
listenerCallback.setSubtitle(wallet.getAccountLabel()); listenerCallback.setSubtitle(wallet.getAccountLabel());
tvAddress.setText(wallet.getAddress()); tvAddress.setText(wallet.getAddress());
enableCopyAddress(true); enableCopyAddress(true);
enableSubaddressButton(true);
hideProgress(); hideProgress();
generateQr(); generateQr();
} }
private void enableCopyAddress(boolean enable) { private void enableCopyAddress(boolean enable) {
bCopyAddress.setClickable(enable); bCopyAddress.setEnabled(enable);
if (enable)
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
else
bCopyAddress.setImageResource(R.drawable.ic_content_nocopy_black_24dp);
} }
private void loadAndShow(String walletPath, String password) { private void loadAndShow(String walletPath, String password) {
@@ -478,7 +469,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(BarcodeData.Asset.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) {
@@ -620,6 +611,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

@@ -19,7 +19,7 @@ package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.support.v4.app.Fragment; import androidx.fragment.app.Fragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;

View File

@@ -17,13 +17,18 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import java.util.Locale;
import static android.view.WindowManager.LayoutParams; import static android.view.WindowManager.LayoutParams;
public abstract class SecureActivity extends AppCompatActivity { public abstract class SecureActivity extends AppCompatActivity {
@@ -37,7 +42,36 @@ public abstract class SecureActivity extends AppCompatActivity {
} }
@Override @Override
protected void attachBaseContext(Context context) { protected void attachBaseContext(Context newBase) {
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context))); super.attachBaseContext(newBase);
applyOverrideConfiguration(new Configuration());
}
@Override
public void applyOverrideConfiguration(Configuration newConfig) {
super.applyOverrideConfiguration(updateConfigurationIfSupported(newConfig));
}
private Configuration updateConfigurationIfSupported(Configuration config) {
// Configuration.getLocales is added after 24 and Configuration.locale is deprecated in 24
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (!config.getLocales().isEmpty()) {
return config;
}
} else {
if (config.locale != null) {
return config;
}
}
Locale locale = LocaleHelper.getPreferredLocale(this);
if (locale != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
return config;
} }
} }

View File

@@ -16,27 +16,31 @@
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.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.text.InputType; import android.text.InputType;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; 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.Button; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer; import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -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();
@@ -242,9 +249,9 @@ public class TxFragment extends Fragment {
} else if (info.isPending) { } else if (info.isPending) {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_pending)); setTxColour(ContextCompat.getColor(getContext(), R.color.tx_pending));
} else if (info.direction == TransactionInfo.Direction.Direction_In) { } else if (info.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_green)); setTxColour(ContextCompat.getColor(getContext(), R.color.tx_plus));
} else { } else {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_red)); setTxColour(ContextCompat.getColor(getContext(), R.color.tx_minus));
} }
Set<String> destinations = new HashSet<>(); Set<String> destinations = new HashSet<>();
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
@@ -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 + " BTC");
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

@@ -16,7 +16,6 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.AlertDialog;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -27,14 +26,6 @@ import android.content.pm.PackageManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -45,6 +36,17 @@ import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.navigation.NavigationView;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData; import com.m2049r.xmrwallet.data.TxData;
import com.m2049r.xmrwallet.data.UserNotes; import com.m2049r.xmrwallet.data.UserNotes;
@@ -58,6 +60,7 @@ import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.ColorHelper;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@@ -312,11 +315,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
private void updateStreetMode() { private void updateStreetMode() {
if (isStreetMode()) {
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_streetmode);
} else {
showNet();
}
invalidateOptionsMenu(); invalidateOptionsMenu();
} }
@@ -426,10 +424,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet); toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
break; break;
case NetworkType_Testnet: case NetworkType_Testnet:
toolbar.setBackgroundResource(R.color.colorPrimaryDark); toolbar.setBackgroundResource(ColorHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
break; break;
case NetworkType_Stagenet: case NetworkType_Stagenet:
toolbar.setBackgroundResource(R.color.colorPrimaryDark); toolbar.setBackgroundResource(ColorHelper.getThemedResourceId(this, R.attr.colorPrimaryDark));
break; break;
default: default:
throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType()); throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
@@ -581,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)
@@ -667,7 +661,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
haveWallet = true; haveWallet = true;
invalidateOptionsMenu(); invalidateOptionsMenu();
enableStreetMode(requestStreetMode); if (requestStreetMode) onEnableStreetMode();
final WalletFragment walletFragment = (WalletFragment) final WalletFragment walletFragment = (WalletFragment)
getSupportFragmentManager().findFragmentById(R.id.fragment_container); getSupportFragmentManager().findFragmentById(R.id.fragment_container);
@@ -872,7 +866,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
} }
}; };
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setMessage(getString(R.string.details_alert_message)) builder.setMessage(getString(R.string.details_alert_message))
.setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener) .setPositiveButton(getString(R.string.details_alert_yes), dialogClickListener)
.setNegativeButton(getString(R.string.details_alert_no), dialogClickListener) .setNegativeButton(getString(R.string.details_alert_no), dialogClickListener)
@@ -1057,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
@@ -1093,7 +1089,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
final LayoutInflater li = LayoutInflater.from(this); final LayoutInflater li = LayoutInflater.from(this);
final View promptsView = li.inflate(R.layout.prompt_rename, null); final View promptsView = li.inflate(R.layout.prompt_rename, null);
final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); final AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(this);
alertDialogBuilder.setView(promptsView); alertDialogBuilder.setView(promptsView);
final EditText etRename = promptsView.findViewById(R.id.etRename); final EditText etRename = promptsView.findViewById(R.id.etRename);

View File

@@ -17,12 +17,10 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@@ -38,6 +36,11 @@ import android.widget.ProgressBar;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener; import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
@@ -70,6 +73,8 @@ public class WalletFragment extends Fragment
private ProgressBar pbProgress; private ProgressBar pbProgress;
private Button bReceive; private Button bReceive;
private Button bSend; private Button bSend;
private ImageView ivStreetGunther;
private Drawable streetGunther = null;
private Spinner sCurrency; private Spinner sCurrency;
@@ -97,11 +102,12 @@ public class WalletFragment extends Fragment
Bundle savedInstanceState) { Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_wallet, container, false); View view = inflater.inflate(R.layout.fragment_wallet, container, false);
ivStreetGunther = view.findViewById(R.id.ivStreetGunther);
tvStreetView = view.findViewById(R.id.tvStreetView); tvStreetView = view.findViewById(R.id.tvStreetView);
llBalance = view.findViewById(R.id.llBalance); llBalance = view.findViewById(R.id.llBalance);
flExchange = view.findViewById(R.id.flExchange); flExchange = view.findViewById(R.id.flExchange);
((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable(). ((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable().
setColorFilter(getResources().getColor(R.color.trafficGray), setColorFilter(getResources().getColor(R.color.progress_circle),
android.graphics.PorterDuff.Mode.MULTIPLY); android.graphics.PorterDuff.Mode.MULTIPLY);
tvProgress = view.findViewById(R.id.tvProgress); tvProgress = view.findViewById(R.id.tvProgress);
@@ -199,13 +205,15 @@ public class WalletFragment extends Fragment
void showBalance(String balance) { void showBalance(String balance) {
tvBalance.setText(balance); tvBalance.setText(balance);
if (!activityCallback.isStreetMode()) { final boolean streetMode = activityCallback.isStreetMode();
if (!streetMode) {
llBalance.setVisibility(View.VISIBLE); llBalance.setVisibility(View.VISIBLE);
tvStreetView.setVisibility(View.INVISIBLE); tvStreetView.setVisibility(View.INVISIBLE);
} else { } else {
llBalance.setVisibility(View.INVISIBLE); llBalance.setVisibility(View.INVISIBLE);
tvStreetView.setVisibility(View.VISIBLE); tvStreetView.setVisibility(View.VISIBLE);
} }
setStreetModeBackground(streetMode);
} }
void showUnconfirmed(double unconfirmedAmount) { void showUnconfirmed(double unconfirmedAmount) {
@@ -537,4 +545,13 @@ public class WalletFragment extends Fragment
} }
} }
public void setStreetModeBackground(boolean enable) {
//TODO figure out why gunther disappears on return from send although he is still set
if (enable) {
if (streetGunther == null)
streetGunther = ContextCompat.getDrawable(getContext(), R.drawable.ic_gunther_streetmode);
ivStreetGunther.setImageDrawable(streetGunther);
} else
ivStreetGunther.setImageDrawable(null);
}
} }

View File

@@ -16,13 +16,15 @@
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 com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper;
import timber.log.Timber; import timber.log.Timber;
@@ -34,18 +36,23 @@ public class XmrWalletApplication extends Application {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree()); Timber.plant(new Timber.DebugTree());
} }
NightmodeHelper.setPreferredNightmode(this);
} }
@Override @Override
protected void attachBaseContext(Context context) { protected void attachBaseContext(Context context) {
super.attachBaseContext(LocaleHelper.setLocale(context, LocaleHelper.getLocale(context))); super.attachBaseContext(LocaleHelper.setPreferredLocale(context));
} }
@Override @Override
public void onConfigurationChanged(Configuration configuration) { public void onConfigurationChanged(Configuration configuration) {
super.onConfigurationChanged(configuration); super.onConfigurationChanged(configuration);
LocaleHelper.updateSystemDefaultLocale(configuration.locale); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
LocaleHelper.setLocale(XmrWalletApplication.this, LocaleHelper.getLocale(XmrWalletApplication.this)); LocaleHelper.updateSystemDefaultLocale(configuration.getLocales().get(0));
} else {
LocaleHelper.updateSystemDefaultLocale(configuration.locale);
}
LocaleHelper.setPreferredLocale(this);
} }
static public NetworkType getNetworkType() { static public NetworkType getNetworkType() {
@@ -54,7 +61,7 @@ public class XmrWalletApplication extends Application {
return NetworkType.NetworkType_Mainnet; return NetworkType.NetworkType_Mainnet;
case "stagenet": case "stagenet":
return NetworkType.NetworkType_Stagenet; return NetworkType.NetworkType_Stagenet;
case "testnet": case "devnet": // flavors cannot start with "test"
return NetworkType.NetworkType_Testnet; return NetworkType.NetworkType_Testnet;
default: default:
throw new IllegalStateException("unknown net flavor " + BuildConfig.FLAVOR_net); throw new IllegalStateException("unknown net flavor " + BuildConfig.FLAVOR_net);

View File

@@ -21,7 +21,6 @@ import android.net.Uri;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.OpenAliasHelper; import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.PaymentProtocolHelper;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@@ -43,7 +42,6 @@ public class BarcodeData {
static final String BTC_SCHEME = "bitcoin"; static final String BTC_SCHEME = "bitcoin";
static final String BTC_DESCRIPTION = "message"; static final String BTC_DESCRIPTION = "message";
static final String BTC_AMOUNT = "amount"; static final String BTC_AMOUNT = "amount";
static final String BTC_BIP70_PARM = "r";
public enum Asset { public enum Asset {
XMR, BTC XMR, BTC
@@ -52,8 +50,7 @@ public class BarcodeData {
public enum Security { public enum Security {
NORMAL, NORMAL,
OA_NO_DNSSEC, OA_NO_DNSSEC,
OA_DNSSEC, OA_DNSSEC
BIP70
} }
final public Asset asset; final public Asset asset;
@@ -62,7 +59,6 @@ public class BarcodeData {
final public String amount; final public String amount;
final public String description; final public String description;
final public Security security; final public Security security;
final public String bip70;
public BarcodeData(Asset asset, String address) { public BarcodeData(Asset asset, String address) {
this(asset, address, null, null, null, Security.NORMAL); this(asset, address, null, null, null, Security.NORMAL);
@@ -80,21 +76,19 @@ public class BarcodeData {
this(asset, address, null, description, amount, Security.NORMAL); this(asset, address, null, description, amount, Security.NORMAL);
} }
public BarcodeData(Asset asset, String address, String addressName, String description, String amount, Security security) { public BarcodeData(Asset asset, String address, String description, String amount) {
this(asset, address, addressName, null, description, amount, security); this(asset, address, null, description, amount, Security.NORMAL);
} }
public BarcodeData(Asset asset, String address, String addressName, String bip70, String description, String amount, Security security) { public BarcodeData(Asset asset, String address, String addressName, String description, String amount, Security security) {
this.asset = asset; this.asset = asset;
this.address = address; this.address = address;
this.bip70 = bip70;
this.addressName = addressName; this.addressName = addressName;
this.description = description; this.description = description;
this.amount = amount; this.amount = amount;
this.security = security; this.security = security;
} }
public Uri getUri() { public Uri getUri() {
return Uri.parse(getUriString()); return Uri.parse(getUriString());
} }
@@ -127,10 +121,6 @@ public class BarcodeData {
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinUri(qrCode); bcData = parseBitcoinUri(qrCode);
} }
// check for btc payment uri (like bitpay)
if (bcData == null) {
bcData = parseBitcoinPaymentUrl(qrCode);
}
// check for naked btc address // check for naked btc address
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinNaked(qrCode); bcData = parseBitcoinNaked(qrCode);
@@ -191,11 +181,11 @@ public class BarcodeData {
} }
} }
if (!Wallet.isAddressValid(address)) { if ((address == null) || !Wallet.isAddressValid(address)) {
Timber.d("address invalid"); Timber.d("address invalid");
return null; return null;
} }
return new BarcodeData(Asset.XMR, address, paymentId, description, amount); return new BarcodeData(Asset.XMR, address, description, amount);
} }
static public BarcodeData parseMoneroNaked(String address) { static public BarcodeData parseMoneroNaked(String address) {
@@ -245,17 +235,9 @@ public class BarcodeData {
} }
String description = parms.get(BTC_DESCRIPTION); String description = parms.get(BTC_DESCRIPTION);
String address = parts[0]; // no need to decode as there can bo no special characters String address = parts[0]; // no need to decode as there can bo no special characters
if (address.isEmpty()) { // possibly a BIP72 uri if (address.isEmpty()) {
String bip70 = parms.get(BTC_BIP70_PARM); Timber.d("no address");
if (bip70 == null) { return null;
Timber.d("no address and can't find pp url");
return null;
}
if (!PaymentProtocolHelper.isHttp(bip70)) {
Timber.d("[%s] is not http url", bip70);
return null;
}
return new BarcodeData(BarcodeData.Asset.BTC, null, null, bip70, description, null, Security.NORMAL);
} }
if (!BitcoinAddressValidator.validate(address)) { if (!BitcoinAddressValidator.validate(address)) {
Timber.d("BTC address (%s) invalid", address); Timber.d("BTC address (%s) invalid", address);
@@ -270,22 +252,7 @@ public class BarcodeData {
return null; // we have an amount but its not a number! return null; // we have an amount but its not a number!
} }
} }
return new BarcodeData(BarcodeData.Asset.BTC, address, null, description, amount); return new BarcodeData(BarcodeData.Asset.BTC, address, description, amount);
}
// https://bitpay.com/invoice?id=xxx
// https://bitpay.com/i/KbMdd4EhnLXSbpWGKsaeo6
static public BarcodeData parseBitcoinPaymentUrl(String url) {
Timber.d("parseBitcoinUri=%s", url);
if (url == null) return null;
if (!PaymentProtocolHelper.isHttp(url)) {
Timber.d("[%s] is not http url", url);
return null;
}
return new BarcodeData(Asset.BTC, url);
} }
static public BarcodeData parseBitcoinNaked(String address) { static public BarcodeData parseBitcoinNaked(String address) {
@@ -333,6 +300,10 @@ public class BarcodeData {
} }
String paymentId = oaAttrs.get(OpenAliasHelper.OA1_PAYMENTID); String paymentId = oaAttrs.get(OpenAliasHelper.OA1_PAYMENTID);
if (paymentId != null) {
Timber.e("paymentId not supported");
return null;
}
String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION); String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION);
if (description == null) { if (description == null) {
description = oaAttrs.get(OpenAliasHelper.OA1_NAME); description = oaAttrs.get(OpenAliasHelper.OA1_NAME);
@@ -348,13 +319,9 @@ public class BarcodeData {
return null; // we have an amount but its not a number! return null; // we have an amount but its not a number!
} }
} }
if ((paymentId != null) && !Wallet.isPaymentIdValid(paymentId)) {
Timber.d("paymentId invalid");
return null;
}
Security sec = dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC; Security sec = dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC;
return new BarcodeData(asset, address, addressName, paymentId, description, amount, sec); return new BarcodeData(asset, address, addressName, description, amount, sec);
} }
} }

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,20 @@ 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 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 +42,11 @@ 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(xmrtoOrderId);
out.writeString(btcAddress); out.writeString(btcAddress);
out.writeString(bip70);
out.writeDouble(btcAmount); out.writeDouble(btcAmount);
} }
@@ -87,21 +63,19 @@ public class TxDataBtc extends TxData {
protected TxDataBtc(Parcel in) { protected TxDataBtc(Parcel in) {
super(in); super(in);
xmrtoUuid = 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(",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();

View File

@@ -16,16 +16,16 @@
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 xmrtoDestination = null; public String xmrtoDestination = null;
@@ -35,13 +35,14 @@ 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.]*)BTC,(\\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); xmrtoDestination = m.group(4);
note = m.group(5);
} else { } else {
note = txNotes; note = txNotes;
} }
@@ -56,12 +57,14 @@ 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());
xmrtoDestination = order.getBtcAddress();
} else { } else {
xmrtoTag = null;
xmrtoKey = null; xmrtoKey = null;
xmrtoAmount = null; xmrtoAmount = null;
xmrtoDestination = null; xmrtoDestination = null;
@@ -70,11 +73,13 @@ 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);

View File

@@ -16,26 +16,25 @@
package com.m2049r.xmrwallet.dialog; package com.m2049r.xmrwallet.dialog;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import android.support.v4.app.Fragment; import androidx.fragment.app.Fragment;
import android.support.v4.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import android.support.v4.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import android.text.Html; import android.text.Html;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.m2049r.xmrwallet.BuildConfig; import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import timber.log.Timber; import timber.log.Timber;
@@ -63,15 +62,15 @@ public class AboutFragment extends DialogFragment {
((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getLicencesHtml())); ((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getLicencesHtml()));
((TextView) view.findViewById(R.id.tvVersion)).setText(getString(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); ((TextView) view.findViewById(R.id.tvVersion)).setText(getString(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
builder.setView(view); .setView(view)
builder.setNegativeButton(R.string.about_close, .setNegativeButton(R.string.about_close,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
dialog.dismiss(); dialog.dismiss();
} }
}); });
return builder.create(); return builder.create();
} }

View File

@@ -16,19 +16,20 @@
package com.m2049r.xmrwallet.dialog; package com.m2049r.xmrwallet.dialog;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.Html; import android.text.Html;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
public class CreditsFragment extends DialogFragment { public class CreditsFragment extends DialogFragment {
@@ -54,16 +55,15 @@ public class CreditsFragment extends DialogFragment {
((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.credits_text))); ((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.credits_text)));
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
builder.setView(view); .setView(view)
builder.setNegativeButton(R.string.about_close, .setNegativeButton(R.string.about_close,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
dialog.dismiss(); dialog.dismiss();
} }
}); });
return builder.create(); return builder.create();
} }
} }

View File

@@ -16,19 +16,20 @@
package com.m2049r.xmrwallet.dialog; package com.m2049r.xmrwallet.dialog;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.Html; import android.text.Html;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
public class HelpFragment extends DialogFragment { public class HelpFragment extends DialogFragment {
@@ -65,15 +66,15 @@ public class HelpFragment extends DialogFragment {
if (helpId > 0) if (helpId > 0)
((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getString(helpId))); ((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getString(helpId)));
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
builder.setView(view); .setView(view)
builder.setNegativeButton(R.string.help_ok, .setNegativeButton(R.string.help_ok,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
dialog.dismiss(); dialog.dismiss();
} }
}); });
return builder.create(); return builder.create();
} }
} }

View File

@@ -16,19 +16,20 @@
package com.m2049r.xmrwallet.dialog; package com.m2049r.xmrwallet.dialog;
import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.text.Html; import android.text.Html;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
public class PrivacyFragment extends DialogFragment { public class PrivacyFragment extends DialogFragment {
@@ -54,15 +55,15 @@ public class PrivacyFragment extends DialogFragment {
((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.privacy_policy))); ((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.privacy_policy)));
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
builder.setView(view); .setView(view)
builder.setNegativeButton(R.string.about_close, .setNegativeButton(R.string.about_close,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
dialog.dismiss(); dialog.dismiss();
} }
}); });
return builder.create(); return builder.create();
} }
} }

View File

@@ -17,7 +17,6 @@ package com.m2049r.xmrwallet.dialog;
* limitations under the License. * limitations under the License.
*/ */
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -26,7 +25,8 @@ import android.view.WindowManager;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import com.m2049r.xmrwallet.BuildConfig; import androidx.appcompat.app.AlertDialog;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;

View File

@@ -19,8 +19,6 @@ package com.m2049r.xmrwallet.fragment.send;
import android.content.Context; import android.content.Context;
import android.nfc.NfcManager; import android.nfc.NfcManager;
import android.os.Bundle; import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.support.v7.widget.CardView;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
@@ -36,6 +34,9 @@ import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import com.google.android.material.textfield.TextInputLayout;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.TxData; import com.m2049r.xmrwallet.data.TxData;
@@ -46,9 +47,6 @@ import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OpenAliasHelper; import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.PaymentProtocolHelper;
import com.m2049r.xmrwallet.xmrto.XmrToError;
import com.m2049r.xmrwallet.xmrto.XmrToException;
import java.util.Map; import java.util.Map;
@@ -86,15 +84,13 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private EditText etDummy; private EditText etDummy;
private TextInputLayout etAddress; private TextInputLayout etAddress;
private TextInputLayout etNotes; private TextInputLayout etNotes;
private CardView cvScan; private View cvScan;
private View tvPaymentIdIntegrated; private View tvPaymentIdIntegrated;
private TextView tvXmrTo; private TextView tvXmrTo;
private View llXmrTo; private View llXmrTo;
private ImageButton bPasteAddress; private ImageButton bPasteAddress;
private boolean resolvingOA = false; private boolean resolvingOA = false;
private boolean resolvingPP = false;
private String resolvedPP = null;
OnScanListener onScanListener; OnScanListener onScanListener;
@@ -126,20 +122,11 @@ public class SendAddressWizardFragment extends SendWizardFragment {
@Override @Override
public void onFocusChange(View v, boolean hasFocus) { public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) { if (!hasFocus) {
View next = etAddress;
String enteredAddress = etAddress.getEditText().getText().toString().trim(); String enteredAddress = etAddress.getEditText().getText().toString().trim();
String dnsOA = dnsFromOpenAlias(enteredAddress); String dnsOA = dnsFromOpenAlias(enteredAddress);
Timber.d("OpenAlias is %s", dnsOA); Timber.d("OpenAlias is %s", dnsOA);
if (dnsOA != null) { if (dnsOA != null) {
processOpenAlias(dnsOA); processOpenAlias(dnsOA);
next = null;
} else {
// maybe a bip72 or 70 URI
final String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
if (bip70 != null) {
// looks good - resolve through xmr.to
processBip70(bip70);
}
} }
} }
} }
@@ -148,8 +135,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
@Override @Override
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
Timber.d("AFTER: %s", editable.toString()); Timber.d("AFTER: %s", editable.toString());
if (editable.toString().equals(resolvedPP)) return; // no change required
resolvedPP = null;
etAddress.setError(null); etAddress.setError(null);
if (isIntegratedAddress()) { if (isIntegratedAddress()) {
Timber.d("isIntegratedAddress"); Timber.d("isIntegratedAddress");
@@ -157,7 +142,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
tvPaymentIdIntegrated.setVisibility(View.VISIBLE); tvPaymentIdIntegrated.setVisibility(View.VISIBLE);
llXmrTo.setVisibility(View.INVISIBLE); llXmrTo.setVisibility(View.INVISIBLE);
sendListener.setMode(SendFragment.Mode.XMR); sendListener.setMode(SendFragment.Mode.XMR);
} else if (isBitcoinAddress() || (resolvedPP != null)) { } else if (isBitcoinAddress()) {
Timber.d("isBitcoinAddress"); Timber.d("isBitcoinAddress");
setBtcMode(); setBtcMode();
} else { } else {
@@ -191,14 +176,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
et.setSelection(et.getText().length()); et.setSelection(et.getText().length());
etAddress.requestFocus(); etAddress.requestFocus();
} else { } else {
final String bip70 = PaymentProtocolHelper.getBip70(clip); Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
if (bip70 != null) {
final EditText et = etAddress.getEditText();
et.setText(clip);
et.setSelection(et.getText().length());
processBip70(bip70);
} else
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
} }
} }
}); });
@@ -274,62 +252,10 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} // else ignore } // else ignore
} }
private void processBip70(final String bip70) {
Timber.d("RESOLVED PP: %s", resolvedPP);
if (resolvingPP) return; // already resolving - just wait
resolvingPP = true;
sendListener.popBarcodeData();
etAddress.setError(getString(R.string.send_address_resolve_bip70));
PaymentProtocolHelper.resolve(bip70, new PaymentProtocolHelper.OnResolvedListener() {
@Override
public void onResolved(BarcodeData.Asset asset, String address, double amount, String resolvedBip70) {
resolvingPP = false;
if (asset != BarcodeData.Asset.BTC)
throw new IllegalArgumentException("only BTC here");
if (resolvedBip70 == null)
throw new IllegalArgumentException("success means we have a pp_url - else die");
final BarcodeData barcodeData =
new BarcodeData(BarcodeData.Asset.BTC, address, null,
resolvedBip70, null, String.valueOf(amount),
BarcodeData.Security.BIP70);
etNotes.post(new Runnable() {
@Override
public void run() {
Timber.d("security is %s", barcodeData.security);
processScannedData(barcodeData);
etNotes.requestFocus();
}
});
}
@Override
public void onFailure(final Exception ex) {
resolvingPP = false;
etAddress.post(new Runnable() {
@Override
public void run() {
int errorMsgId = R.string.send_address_not_bip70;
if (ex instanceof XmrToException) {
XmrToError error = ((XmrToException) ex).getError();
if (error != null) {
errorMsgId = error.getErrorMsgId();
}
}
etAddress.setError(getString(errorMsgId));
}
});
Timber.d("PP FAILED");
}
});
}
private boolean checkAddressNoError() { private boolean checkAddressNoError() {
String address = etAddress.getEditText().getText().toString(); String address = etAddress.getEditText().getText().toString();
return Wallet.isAddressValid(address) return Wallet.isAddressValid(address)
|| BitcoinAddressValidator.validate(address) || BitcoinAddressValidator.validate(address);
|| (resolvedPP != null);
} }
private boolean checkAddress() { private boolean checkAddress() {
@@ -366,11 +292,6 @@ public class SendAddressWizardFragment extends SendWizardFragment {
Timber.d("OpenAlias is %s", dnsOA); Timber.d("OpenAlias is %s", dnsOA);
if (dnsOA != null) { if (dnsOA != null) {
processOpenAlias(dnsOA); processOpenAlias(dnsOA);
} else {
String bip70 = PaymentProtocolHelper.getBip70(enteredAddress);
if (bip70 != null) {
processBip70(bip70);
}
} }
return false; return false;
} }
@@ -378,15 +299,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
if (sendListener != null) { if (sendListener != null) {
TxData txData = sendListener.getTxData(); TxData txData = sendListener.getTxData();
if (txData instanceof TxDataBtc) { if (txData instanceof TxDataBtc) {
if (resolvedPP != null) { ((TxDataBtc) txData).setBtcAddress(etAddress.getEditText().getText().toString());
// take the value from the field nonetheless as this is what the user sees
// (in case we have a bug somewhere)
((TxDataBtc) txData).setBip70(etAddress.getEditText().getText().toString());
((TxDataBtc) txData).setBtcAddress(null);
} else {
((TxDataBtc) txData).setBtcAddress(etAddress.getEditText().getText().toString());
((TxDataBtc) txData).setBip70(null);
}
txData.setDestinationAddress(null); txData.setDestinationAddress(null);
} else { } else {
txData.setDestinationAddress(etAddress.getEditText().getText().toString()); txData.setDestinationAddress(etAddress.getEditText().getText().toString());
@@ -399,7 +312,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof OnScanListener) { if (context instanceof OnScanListener) {
onScanListener = (OnScanListener) context; onScanListener = (OnScanListener) context;
@@ -425,21 +338,11 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} }
public void processScannedData() { public void processScannedData() {
resolvedPP = null;
BarcodeData barcodeData = sendListener.getBarcodeData(); BarcodeData barcodeData = sendListener.getBarcodeData();
if (barcodeData != null) { if (barcodeData != null) {
Timber.d("GOT DATA"); Timber.d("GOT DATA");
if (barcodeData.bip70 != null) { if (barcodeData.address != null) {
setBtcMode();
if (barcodeData.security == BarcodeData.Security.BIP70) {
resolvedPP = barcodeData.bip70;
etAddress.setError(getString(R.string.send_address_bip70));
} else {
processBip70(barcodeData.bip70);
}
etAddress.getEditText().setText(barcodeData.bip70);
} else if (barcodeData.address != null) {
etAddress.getEditText().setText(barcodeData.address); etAddress.getEditText().setText(barcodeData.address);
if (checkAddress()) { if (checkAddress()) {
if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC) if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC)

View File

@@ -148,12 +148,9 @@ public class SendAmountWizardFragment extends SendWizardFragment {
tvFunds.setText(getString(R.string.send_available, tvFunds.setText(getString(R.string.send_available,
getString(R.string.unknown_amount))); getString(R.string.unknown_amount)));
} }
// getNativeAmount is null if exchange is in progress final BarcodeData data = sendListener.popBarcodeData();
if ((etAmount.getNativeAmount() != null) && etAmount.getNativeAmount().isEmpty()) { if ((data != null) && (data.amount != null)) {
final BarcodeData data = sendListener.popBarcodeData(); etAmount.setAmount(data.amount);
if ((data != null) && (data.amount != null)) {
etAmount.setAmount(data.amount);
}
} }
} }

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