mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 17:28:42 +02:00
Compare commits
64 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
093e4bda1d | ||
![]() |
4aa7000cb3 | ||
![]() |
62844192df | ||
![]() |
855b35a6b7 | ||
![]() |
88110b702f | ||
![]() |
0013caa05f | ||
![]() |
5b3e92e91a | ||
![]() |
f8ea3cc77f | ||
![]() |
59ef2a1f8e | ||
![]() |
fae07ed716 | ||
![]() |
e662d5a9c0 | ||
![]() |
4b2d52fbe6 | ||
![]() |
ec7798cb34 | ||
![]() |
20e7d6d065 | ||
![]() |
89ada5b294 | ||
![]() |
fe80fef36e | ||
![]() |
e8331dda7a | ||
![]() |
68abaec6cb | ||
![]() |
fe84bae9ed | ||
![]() |
1289a0ada4 | ||
![]() |
93025c5e1b | ||
![]() |
e0439a1359 | ||
![]() |
93ec3865da | ||
![]() |
49e338e80d | ||
![]() |
075ddff226 | ||
![]() |
7eaf17d48e | ||
![]() |
6822aa83d8 | ||
![]() |
20dbaac4fa | ||
![]() |
726887af2e | ||
![]() |
03da2880ac | ||
![]() |
78c053f4e8 | ||
![]() |
5b38cc438c | ||
![]() |
1eac363102 | ||
![]() |
e51425bc2e | ||
![]() |
f5535dbefa | ||
![]() |
2d42a0287d | ||
![]() |
2ebb9d0650 | ||
![]() |
30792c53c0 | ||
![]() |
3846a0c391 | ||
![]() |
c66ebd654a | ||
![]() |
186ed5cd39 | ||
![]() |
e39cd1c988 | ||
![]() |
39d9c4d0c3 | ||
![]() |
e4562f07a4 | ||
![]() |
b3ea4540a1 | ||
![]() |
ea7c7d2fdb | ||
![]() |
8ff8be6f60 | ||
![]() |
b5ded700fe | ||
![]() |
a5527d4efd | ||
![]() |
a007810824 | ||
![]() |
a634cf18b1 | ||
![]() |
0b2fa08df8 | ||
![]() |
191aa6ac22 | ||
![]() |
a82a90575d | ||
![]() |
2e04046f50 | ||
![]() |
c11d577c5f | ||
![]() |
74b2dd209c | ||
![]() |
cb1d541474 | ||
![]() |
807d217aac | ||
![]() |
0421bcfac3 | ||
![]() |
b6c4e06fda | ||
![]() |
3ee8343074 | ||
![]() |
110621824a | ||
![]() |
7194580fbe |
2
.idea/.gitignore
generated
vendored
2
.idea/.gitignore
generated
vendored
@@ -1,2 +1,2 @@
|
||||
workspace.xml
|
||||
markdown-navigator*
|
||||
markdown-*
|
||||
|
10
.idea/libraries/core_1_9_8.xml
generated
Normal file
10
.idea/libraries/core_1_9_8.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="core-1.9.8">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.android/build-cache/db7eb580bfa6467818ef12c5f1fa42273b50aa3a/output/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.android/build-cache/db7eb580bfa6467818ef12c5f1fa42273b50aa3a/output/res" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
11
.idea/libraries/core_3_3_0.xml
generated
Normal file
11
.idea/libraries/core_3_3_0.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
<component name="libraryTable">
|
||||
<library name="core-3.3.0">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.zxing/core/3.3.0/73c49077166faa4c3c0059c5f583d1d7bd1475fe/core-3.3.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES>
|
||||
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.zxing/core/3.3.0/39d966e052e28fc7d83793b02e0af97ccf955745/core-3.3.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
10
.idea/libraries/zxing_1_9_8.xml
generated
Normal file
10
.idea/libraries/zxing_1_9_8.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="libraryTable">
|
||||
<library name="zxing-1.9.8">
|
||||
<CLASSES>
|
||||
<root url="jar://$USER_HOME$/.android/build-cache/8d8f2e0e10c7af73321454f6fa481e8e5438e654/output/jars/classes.jar!/" />
|
||||
<root url="file://$USER_HOME$/.android/build-cache/8d8f2e0e10c7af73321454f6fa481e8e5438e654/output/res" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
16
README.md
16
README.md
@@ -8,12 +8,11 @@ Another Android Monero Wallet
|
||||
- see the [FAQ](doc/FAQ.md)
|
||||
|
||||
### Disclaimer
|
||||
You may loose all your Moneroj if you use this App.
|
||||
You may loose all your Moneroj if you use this App. Be cautious when spending on mainnet.
|
||||
|
||||
### Random Notes
|
||||
- Based off monero v0.10.3.1 with pull requests #2238, #2239 and #2289 applied => so can be used in mainnet!
|
||||
- currently only android32
|
||||
- ~~currently only use is checking incoming/outgoing transactions~~
|
||||
- currently only android32 (runs on 64-bit as well)
|
||||
- works in testnet & mainnet
|
||||
- takes forever to sync due to 32-bit architecture
|
||||
- use your own daemon - it's easy
|
||||
@@ -22,16 +21,12 @@ You may loose all your Moneroj if you use this App.
|
||||
- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/
|
||||
|
||||
### TODO
|
||||
- wallet backup functions
|
||||
- adjust layout so we can use bigger font sizes (maybe show only 5 decimal places instead of 12 in main view)
|
||||
- review visibility of methods/classes
|
||||
- more sensible error dialogs ~~(e.g. when no write permissions granted) instead of just crashing on purpose~~
|
||||
- more sensible error dialogs
|
||||
- check licenses of included libraries; License Dialog
|
||||
- ~~make it pretty~~ (decided to go with "form follows function")
|
||||
- ~~spend monero - not so difficult with wallet api~~
|
||||
|
||||
### Issues
|
||||
- Pending incoming transactions disappear after reload
|
||||
- Pending incoming transactions disappear after reload (and appear after being mined)
|
||||
|
||||
### HOW TO BUILD
|
||||
No need to build. Binaries are included:
|
||||
@@ -45,4 +40,5 @@ If you want to build them yourself (recommended) check out [the instructions](do
|
||||
Then, fire up Android Studio and build the APK.
|
||||
|
||||
### Donations
|
||||
4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk
|
||||
- Address: 4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk
|
||||
- Viewkey: b1aff2a12191723da0afbe75516f94dd8b068215f6e847d8da57aca5f1f98e0c
|
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 5
|
||||
versionName "0.4.1"
|
||||
versionCode 11
|
||||
versionName "0.6"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@@ -46,5 +46,6 @@ dependencies {
|
||||
compile 'com.android.support:support-v4:25.3.1'
|
||||
compile 'com.android.support:recyclerview-v7:25.3.1'
|
||||
compile 'com.android.support:cardview-v7:25.3.1'
|
||||
compile 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
|
@@ -7,10 +7,11 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:icon="@drawable/ic_monero_32dp"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
@@ -461,7 +461,6 @@ Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobj
|
||||
|
||||
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
|
||||
|
||||
// actually a WalletManager function, but logically in onWalletSelected
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_closeJ(JNIEnv *env, jobject instance,
|
||||
jobject walletInstance) {
|
||||
@@ -592,12 +591,16 @@ Java_com_m2049r_xmrwallet_model_Wallet_getFilename(JNIEnv *env, jobject instance
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_Wallet_initJ(JNIEnv *env, jobject instance,
|
||||
jstring daemon_address,
|
||||
jlong upper_transaction_size_limit) {
|
||||
// const std::string &daemon_username = "", const std::string &daemon_password = "") = 0;
|
||||
jlong upper_transaction_size_limit,
|
||||
jstring daemon_username, jstring daemon_password) {
|
||||
const char *_daemon_address = env->GetStringUTFChars(daemon_address, JNI_FALSE);
|
||||
const char *_daemon_username = env->GetStringUTFChars(daemon_username, JNI_FALSE);
|
||||
const char *_daemon_password = env->GetStringUTFChars(daemon_password, JNI_FALSE);
|
||||
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
|
||||
bool status = wallet->init(_daemon_address, upper_transaction_size_limit);
|
||||
bool status = wallet->init(_daemon_address, upper_transaction_size_limit, _daemon_username, _daemon_password);
|
||||
env->ReleaseStringUTFChars(daemon_address, _daemon_address);
|
||||
env->ReleaseStringUTFChars(daemon_username, _daemon_username);
|
||||
env->ReleaseStringUTFChars(daemon_password, _daemon_password);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@@ -16,12 +16,13 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -68,12 +69,9 @@ public class GenerateFragment extends Fragment {
|
||||
bGenerate = (Button) view.findViewById(R.id.bGenerate);
|
||||
|
||||
etWalletMnemonic.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etWalletAddress.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etWalletViewKey.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etWalletSpendKey.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
boolean testnet = WalletManager.getInstance().isTestNet();
|
||||
//etWalletMnemonic.setTextIsSelectable(testnet);
|
||||
etWalletAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etWalletViewKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etWalletSpendKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
|
||||
Helper.showKeyboard(getActivity());
|
||||
etWalletName.addTextChangedListener(new TextWatcher() {
|
||||
@@ -297,8 +295,12 @@ public class GenerateFragment extends Fragment {
|
||||
private void generateWallet() {
|
||||
String name = etWalletName.getText().toString();
|
||||
if (name.length() == 0) return;
|
||||
String walletPath = Helper.getWalletPath(getActivity(), name);
|
||||
if (WalletManager.getInstance().walletExists(walletPath)) {
|
||||
if (name.charAt(0)=='.') {
|
||||
Toast.makeText(getActivity(), getString(R.string.generate_wallet_dot), Toast.LENGTH_LONG).show();
|
||||
etWalletName.requestFocus();
|
||||
}
|
||||
File walletFile = Helper.getWalletFile(getActivity(), name);
|
||||
if (WalletManager.getInstance().walletExists(walletFile)) {
|
||||
Toast.makeText(getActivity(), getString(R.string.generate_wallet_exists), Toast.LENGTH_LONG).show();
|
||||
etWalletName.requestFocus();
|
||||
return;
|
||||
@@ -344,6 +346,13 @@ public class GenerateFragment extends Fragment {
|
||||
bGenerate.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(TAG, "onResume()");
|
||||
activityCallback.setTitle(getString(R.string.generate_title));
|
||||
}
|
||||
|
||||
GenerateFragment.Listener activityCallback;
|
||||
|
||||
public interface Listener {
|
||||
@@ -354,6 +363,9 @@ public class GenerateFragment extends Fragment {
|
||||
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
|
||||
|
||||
File getStorageRoot();
|
||||
|
||||
void setTitle(String title);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -16,9 +16,11 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -28,15 +30,13 @@ import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
|
||||
|
||||
import java.io.File;
|
||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||
|
||||
public class GenerateReviewFragment extends Fragment {
|
||||
static final String TAG = "GenerateReviewFragment";
|
||||
static final public String VIEW_DETAILS = "details";
|
||||
static final public String VIEW_ACCEPT = "accept";
|
||||
static final public String VIEW_WALLET = "wallet";
|
||||
static final public String VIEW_TYPE_DETAILS = "details";
|
||||
static final public String VIEW_TYPE_ACCEPT = "accept";
|
||||
static final public String VIEW_TYPE_WALLET = "wallet";
|
||||
|
||||
ProgressBar pbProgress;
|
||||
TextView tvWalletName;
|
||||
@@ -65,6 +65,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
|
||||
boolean testnet = WalletManager.getInstance().isTestNet();
|
||||
tvWalletMnemonic.setTextIsSelectable(testnet);
|
||||
tvWalletSpendKey.setTextIsSelectable(testnet);
|
||||
|
||||
bAccept.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@@ -75,16 +76,12 @@ public class GenerateReviewFragment extends Fragment {
|
||||
|
||||
showProgress();
|
||||
|
||||
Bundle b = getArguments();
|
||||
String type = b.getString("type");
|
||||
if (!type.equals(VIEW_WALLET)) {
|
||||
String name = b.getString("name");
|
||||
String password = b.getString("password");
|
||||
tvWalletName.setText(new File(name).getName());
|
||||
show(name, password, type);
|
||||
} else {
|
||||
show(walletCallback.getWallet(), null, type);
|
||||
}
|
||||
Bundle args = getArguments();
|
||||
String path = args.getString("path");
|
||||
String password = args.getString("password");
|
||||
String type = args.getString("type");
|
||||
new AsyncShow().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR,
|
||||
path, password, type);
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -95,40 +92,64 @@ public class GenerateReviewFragment extends Fragment {
|
||||
acceptCallback.onAccept(name, password);
|
||||
}
|
||||
|
||||
private void show(final String walletPath, final String password, final String type) {
|
||||
new Thread(null,
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password);
|
||||
getActivity().runOnUiThread(new Runnable() {
|
||||
public void run() {
|
||||
show(wallet, password, type);
|
||||
wallet.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
, "DetailsReview", MoneroHandlerThread.THREAD_STACK_SIZE).start();
|
||||
}
|
||||
private class AsyncShow extends AsyncTask<String, Void, Boolean> {
|
||||
String type;
|
||||
String password;
|
||||
|
||||
private void show(final Wallet wallet, final String password, final String type) {
|
||||
if (type.equals(GenerateReviewFragment.VIEW_ACCEPT)) {
|
||||
tvWalletPassword.setText(password);
|
||||
bAccept.setVisibility(View.VISIBLE);
|
||||
bAccept.setEnabled(true);
|
||||
String name;
|
||||
String address;
|
||||
String seed;
|
||||
String viewKey;
|
||||
boolean isWatchOnly;
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(String... params) {
|
||||
if (params.length != 3) return false;
|
||||
String walletPath = params[0];
|
||||
password = params[1];
|
||||
type = params[2];
|
||||
|
||||
Wallet wallet;
|
||||
boolean closeWallet;
|
||||
if (type.equals(GenerateReviewFragment.VIEW_TYPE_WALLET)) {
|
||||
wallet = GenerateReviewFragment.this.walletCallback.getWallet();
|
||||
closeWallet = false;
|
||||
} else {
|
||||
wallet = WalletManager.getInstance().openWallet(walletPath, password);
|
||||
closeWallet = true;
|
||||
}
|
||||
if (wallet.getStatus() != Wallet.Status.Status_Ok) return false;
|
||||
name = wallet.getName();
|
||||
address = wallet.getAddress();
|
||||
seed = wallet.getSeed();
|
||||
viewKey = wallet.getSecretViewKey();
|
||||
isWatchOnly = wallet.isWatchOnly();
|
||||
if (closeWallet) wallet.close();
|
||||
return true;
|
||||
}
|
||||
tvWalletName.setText(wallet.getName());
|
||||
tvWalletAddress.setText(wallet.getAddress());
|
||||
tvWalletMnemonic.setText(wallet.getSeed());
|
||||
tvWalletViewKey.setText(wallet.getSecretViewKey());
|
||||
String spend = wallet.isWatchOnly() ? "" : "not available - use seed for recovery";
|
||||
if (spend.length() > 0) { //TODO should be == 64, but spendkey is not in the API yet
|
||||
tvWalletSpendKey.setText(spend);
|
||||
} else {
|
||||
tvWalletSpendKey.setText(getString(R.string.generate_wallet_watchonly));
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
super.onPostExecute(result);
|
||||
if (result) {
|
||||
if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) {
|
||||
tvWalletPassword.setText(password);
|
||||
bAccept.setVisibility(View.VISIBLE);
|
||||
bAccept.setEnabled(true);
|
||||
}
|
||||
tvWalletName.setText(name);
|
||||
tvWalletAddress.setText(address);
|
||||
tvWalletMnemonic.setText(seed);
|
||||
tvWalletViewKey.setText(viewKey);
|
||||
String spend = isWatchOnly ? "" : "not available - use seed for recovery";
|
||||
if (spend.length() > 0) { //TODO should be == 64, but spendkey is not in the API yet
|
||||
tvWalletSpendKey.setText(spend);
|
||||
} else {
|
||||
tvWalletSpendKey.setText(getString(R.string.generate_wallet_watchonly));
|
||||
}
|
||||
}
|
||||
hideProgress();
|
||||
}
|
||||
hideProgress();
|
||||
}
|
||||
|
||||
GenerateReviewFragment.Listener acceptCallback = null;
|
||||
@@ -140,6 +161,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
|
||||
public interface ListenerWithWallet {
|
||||
Wallet getWallet();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
349
app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
Normal file
349
app/src/main/java/com/m2049r/xmrwallet/ReceiveFragment.java
Normal file
File diff suppressed because it is too large
Load Diff
112
app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java
Normal file
112
app/src/main/java/com/m2049r/xmrwallet/ScannerFragment.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2017 dm77, 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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.Result;
|
||||
import com.m2049r.xmrwallet.util.BarcodeData;
|
||||
|
||||
import me.dm7.barcodescanner.zxing.ZXingScannerView;
|
||||
|
||||
public class ScannerFragment extends Fragment implements ZXingScannerView.ResultHandler {
|
||||
static final String TAG = "ScannerFragment";
|
||||
|
||||
Listener activityCallback;
|
||||
|
||||
public interface Listener {
|
||||
boolean onAddressScanned(String uri);
|
||||
}
|
||||
|
||||
private ZXingScannerView mScannerView;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreateView");
|
||||
mScannerView = new ZXingScannerView(getActivity());
|
||||
return mScannerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(TAG, "onResume");
|
||||
mScannerView.setResultHandler(this);
|
||||
mScannerView.startCamera();
|
||||
}
|
||||
|
||||
static final String QR_SCHEME = "monero:";
|
||||
static final String QR_PAYMENTID = "tx_payment_id";
|
||||
static final String QR_AMOUNT = "tx_amount";
|
||||
|
||||
@Override
|
||||
public void handleResult(Result rawResult) {
|
||||
//Log.d(TAG, rawResult.getBarcodeFormat().toString() + "/" + rawResult.getText());
|
||||
if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE) &&
|
||||
(rawResult.getText().startsWith(QR_SCHEME))) {
|
||||
if (activityCallback.onAddressScanned(rawResult.getText())) {
|
||||
return;
|
||||
} else {
|
||||
Toast.makeText(getActivity(), getString(R.string.send_qr_address_invalid), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getActivity(), getString(R.string.send_qr_invalid), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
// Note from dm77:
|
||||
// * Wait 2 seconds to resume the preview.
|
||||
// * On older devices continuously stopping and resuming camera preview can result in freezing the app.
|
||||
// * I don't know why this is the case but I don't have the time to figure out.
|
||||
Handler handler = new Handler();
|
||||
handler.postDelayed(new
|
||||
|
||||
Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mScannerView.resumeCameraPreview(ScannerFragment.this);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
Log.d(TAG, "onPause");
|
||||
mScannerView.stopCamera();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
//Log.d(TAG, "attaching scan");
|
||||
if (context instanceof Listener) {
|
||||
this.activityCallback = (Listener) context;
|
||||
} else {
|
||||
throw new ClassCastException(context.toString()
|
||||
+ " must implement Listener");
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,12 +16,11 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
@@ -40,23 +39,24 @@ import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||
import com.m2049r.xmrwallet.model.Transfer;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.BarcodeData;
|
||||
import com.m2049r.xmrwallet.util.TxData;
|
||||
|
||||
public class SendFragment extends Fragment {
|
||||
static final String TAG = "GenerateFragment";
|
||||
static final String TAG = "SendFragment";
|
||||
|
||||
EditText etAddress;
|
||||
EditText etPaymentId;
|
||||
EditText etAmount;
|
||||
Button bScan;
|
||||
Button bSweep;
|
||||
Spinner sMixin;
|
||||
Spinner sPriority;
|
||||
Button bPrepareSend;
|
||||
Button bDispose;
|
||||
Button bPaymentId;
|
||||
LinearLayout llConfirmSend;
|
||||
TextView tvTxAmount;
|
||||
@@ -64,6 +64,7 @@ public class SendFragment extends Fragment {
|
||||
TextView tvTxDust;
|
||||
EditText etNotes;
|
||||
Button bSend;
|
||||
Button bReallySend;
|
||||
ProgressBar pbProgress;
|
||||
|
||||
final static int Mixins[] = {4, 6, 8, 10, 13}; // must match the layout XML
|
||||
@@ -83,9 +84,11 @@ public class SendFragment extends Fragment {
|
||||
etAddress = (EditText) view.findViewById(R.id.etAddress);
|
||||
etPaymentId = (EditText) view.findViewById(R.id.etPaymentId);
|
||||
etAmount = (EditText) view.findViewById(R.id.etAmount);
|
||||
bScan = (Button) view.findViewById(R.id.bScan);
|
||||
bSweep = (Button) view.findViewById(R.id.bSweep);
|
||||
bPrepareSend = (Button) view.findViewById(R.id.bPrepareSend);
|
||||
bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
|
||||
bDispose = (Button) view.findViewById(R.id.bDispose);
|
||||
|
||||
llConfirmSend = (LinearLayout) view.findViewById(R.id.llConfirmSend);
|
||||
tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount);
|
||||
@@ -93,17 +96,14 @@ public class SendFragment extends Fragment {
|
||||
tvTxDust = (TextView) view.findViewById(R.id.tvTxDust);
|
||||
etNotes = (EditText) view.findViewById(R.id.etNotes);
|
||||
bSend = (Button) view.findViewById(R.id.bSend);
|
||||
bReallySend = (Button) view.findViewById(R.id.bReallySend);
|
||||
|
||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
||||
|
||||
etAddress.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etPaymentId.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
etAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||
|
||||
// etAddress.setText("9tDC52GsMjTNt4dpnRCwAF7ekVBkbkgkXGaMKTcSTpBhGpqkPX56jCNRydLq9oGjbbAQBsZhLfgmTKsntmxRd3TaJFYM2f8");
|
||||
boolean testnet = WalletManager.getInstance().isTestNet();
|
||||
if (!testnet) throw new IllegalStateException("Sending TX only on testnet. sorry.");
|
||||
|
||||
Helper.showKeyboard(getActivity());
|
||||
etAddress.requestFocus();
|
||||
etAddress.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@@ -191,10 +191,26 @@ public class SendFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
bDispose.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activityCallback.onDisposeRequest();
|
||||
enableEdit();
|
||||
}
|
||||
});
|
||||
|
||||
bScan.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activityCallback.onScanAddress();
|
||||
}
|
||||
});
|
||||
|
||||
bPaymentId.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
etPaymentId.setText((activityCallback.generatePaymentId()));
|
||||
etPaymentId.setText((Wallet.generatePaymentId()));
|
||||
etPaymentId.setSelection(etPaymentId.getText().length());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -224,6 +240,26 @@ public class SendFragment extends Fragment {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
bSend.setEnabled(false);
|
||||
boolean testnet = WalletManager.getInstance().isTestNet();
|
||||
if (testnet) {
|
||||
send();
|
||||
} else {
|
||||
etNotes.setEnabled(false);
|
||||
Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
bReallySend.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bReallySend.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
bReallySend.setEnabled(false);
|
||||
send();
|
||||
}
|
||||
});
|
||||
@@ -240,11 +276,7 @@ public class SendFragment extends Fragment {
|
||||
|
||||
private boolean addressOk() {
|
||||
String address = etAddress.getText().toString();
|
||||
if (WalletManager.getInstance().isTestNet()) {
|
||||
return ((address.length() == 95) && ("9A".indexOf(address.charAt(0)) >= 0));
|
||||
} else {
|
||||
return ((address.length() == 95) && ("4".indexOf(address.charAt(0)) >= 0));
|
||||
}
|
||||
return Helper.isAddressOk(address, WalletManager.getInstance().isTestNet());
|
||||
}
|
||||
|
||||
private boolean amountOk() {
|
||||
@@ -254,7 +286,7 @@ public class SendFragment extends Fragment {
|
||||
|
||||
private boolean paymentIdOk() {
|
||||
String paymentId = etPaymentId.getText().toString();
|
||||
return paymentId.isEmpty() || activityCallback.isPaymentIdValid(paymentId);
|
||||
return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
|
||||
}
|
||||
|
||||
private void prepareSend() {
|
||||
@@ -290,6 +322,7 @@ public class SendFragment extends Fragment {
|
||||
etAddress.setEnabled(false);
|
||||
etPaymentId.setEnabled(false);
|
||||
etAmount.setEnabled(false);
|
||||
bScan.setEnabled(false);
|
||||
bPaymentId.setEnabled(false);
|
||||
bSweep.setEnabled(false);
|
||||
bPrepareSend.setEnabled(false);
|
||||
@@ -301,9 +334,14 @@ public class SendFragment extends Fragment {
|
||||
etAddress.setEnabled(true);
|
||||
etPaymentId.setEnabled(true);
|
||||
etAmount.setEnabled(true);
|
||||
bScan.setEnabled(true);
|
||||
bPaymentId.setEnabled(true);
|
||||
bSweep.setEnabled(true);
|
||||
bPrepareSend.setEnabled(true);
|
||||
llConfirmSend.setVisibility(View.GONE);
|
||||
bSend.setEnabled(true);
|
||||
etNotes.setEnabled(true);
|
||||
bReallySend.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void send() {
|
||||
@@ -321,12 +359,53 @@ public class SendFragment extends Fragment {
|
||||
|
||||
void onSend(String notes);
|
||||
|
||||
String generatePaymentId();
|
||||
|
||||
boolean isPaymentIdValid(String paymentId);
|
||||
|
||||
String getWalletAddress();
|
||||
|
||||
void onDisposeRequest();
|
||||
|
||||
void onScanAddress();
|
||||
|
||||
BarcodeData getScannedData();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Log.d(TAG, "onResume");
|
||||
BarcodeData data = activityCallback.getScannedData();
|
||||
if (data != null) {
|
||||
String scannedAddress = data.address;
|
||||
if (scannedAddress != null) {
|
||||
etAddress.setText(scannedAddress);
|
||||
} else {
|
||||
etAddress.getText().clear();
|
||||
}
|
||||
String scannedPaymenId = data.paymentId;
|
||||
if (scannedPaymenId != null) {
|
||||
etPaymentId.setText(scannedPaymenId);
|
||||
} else {
|
||||
etPaymentId.getText().clear();
|
||||
}
|
||||
if (data.amount >= 0) {
|
||||
String scannedAmount = Helper.getDisplayAmount(data.amount);
|
||||
etAmount.setText(scannedAmount);
|
||||
} else {
|
||||
etAmount.getText().clear();
|
||||
}
|
||||
etAmount.requestFocus();
|
||||
etAmount.setSelection(etAmount.getText().length());
|
||||
} else { // no scan data
|
||||
// jump to first empty field
|
||||
if (etAddress.getText().toString().isEmpty()) {
|
||||
etAddress.requestFocus();
|
||||
} else if (etPaymentId.getText().toString().isEmpty()) {
|
||||
etPaymentId.requestFocus();
|
||||
} else {
|
||||
etAmount.requestFocus();
|
||||
etAmount.setSelection(etAmount.getText().length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -350,7 +429,6 @@ public class SendFragment extends Fragment {
|
||||
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount()));
|
||||
tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee()));
|
||||
tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust()));
|
||||
bSend.setEnabled(true);
|
||||
}
|
||||
|
||||
public void onCreatedTransactionFailed(String errorText) {
|
||||
|
@@ -16,31 +16,28 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||
import com.m2049r.xmrwallet.model.Transfer;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
|
||||
public class TxFragment extends Fragment {
|
||||
@@ -48,7 +45,7 @@ public class TxFragment extends Fragment {
|
||||
|
||||
static public final String ARG_INFO = "info";
|
||||
|
||||
private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
|
||||
|
||||
public TxFragment() {
|
||||
super();
|
||||
@@ -60,6 +57,7 @@ public class TxFragment extends Fragment {
|
||||
TextView tvTxTimestamp;
|
||||
TextView tvTxId;
|
||||
TextView tvTxKey;
|
||||
TextView tvDestination;
|
||||
TextView tvTxPaymentId;
|
||||
TextView tvTxBlockheight;
|
||||
TextView tvTxAmount;
|
||||
@@ -78,6 +76,7 @@ public class TxFragment extends Fragment {
|
||||
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
|
||||
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
|
||||
tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
|
||||
tvDestination = (TextView) view.findViewById(R.id.tvDestination);
|
||||
tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
|
||||
tvTxBlockheight = (TextView) view.findViewById(R.id.tvTxBlockheight);
|
||||
tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount);
|
||||
@@ -142,10 +141,10 @@ public class TxFragment extends Fragment {
|
||||
sb.append(getString(R.string.tx_timestamp)).append(": ");
|
||||
sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n");
|
||||
sb.append(getString(R.string.tx_blockheight)).append(": ");
|
||||
if (info.isPending) {
|
||||
sb.append(getString(R.string.tx_pending)).append("\n");
|
||||
} else if (info.isFailed) {
|
||||
if (info.isFailed) {
|
||||
sb.append(getString(R.string.tx_failed)).append("\n");
|
||||
} else if (info.isPending) {
|
||||
sb.append(getString(R.string.tx_pending)).append("\n");
|
||||
} else {
|
||||
sb.append(info.blockheight).append("\n");
|
||||
}
|
||||
@@ -154,11 +153,11 @@ public class TxFragment extends Fragment {
|
||||
boolean comma = false;
|
||||
for (Transfer transfer : info.transfers) {
|
||||
if (comma) {
|
||||
sb.append(",");
|
||||
sb.append(", ");
|
||||
} else {
|
||||
comma = true;
|
||||
}
|
||||
sb.append("[").append(transfer.address.substring(0, 6)).append("] ");
|
||||
sb.append(transfer.address).append(": ");
|
||||
sb.append(Wallet.getDisplayAmount(transfer.amount));
|
||||
}
|
||||
} else {
|
||||
@@ -192,20 +191,23 @@ public class TxFragment extends Fragment {
|
||||
tvTxId.setText(info.hash);
|
||||
tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey);
|
||||
tvTxPaymentId.setText(info.paymentId);
|
||||
if (info.isPending) {
|
||||
tvTxBlockheight.setText(getString(R.string.tx_pending));
|
||||
} else if (info.isFailed) {
|
||||
if (info.isFailed) {
|
||||
tvTxBlockheight.setText(getString(R.string.tx_failed));
|
||||
} else if (info.isPending) {
|
||||
tvTxBlockheight.setText(getString(R.string.tx_pending));
|
||||
} else {
|
||||
tvTxBlockheight.setText("" + info.blockheight);
|
||||
}
|
||||
String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-");
|
||||
tvTxAmount.setText(sign + Wallet.getDisplayAmount(info.amount));
|
||||
tvTxFee.setText(Wallet.getDisplayAmount(info.fee));
|
||||
Set<String> destinations = new HashSet<>();
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuffer dstSb = new StringBuffer();
|
||||
if (info.transfers != null) {
|
||||
boolean newline = false;
|
||||
for (Transfer transfer : info.transfers) {
|
||||
destinations.add(transfer.address);
|
||||
if (newline) {
|
||||
sb.append("\n");
|
||||
} else {
|
||||
@@ -214,10 +216,21 @@ public class TxFragment extends Fragment {
|
||||
sb.append("[").append(transfer.address.substring(0, 6)).append("] ");
|
||||
sb.append(Wallet.getDisplayAmount(transfer.amount));
|
||||
}
|
||||
newline = false;
|
||||
for (String dst : destinations) {
|
||||
if (newline) {
|
||||
dstSb.append("\n");
|
||||
} else {
|
||||
newline = true;
|
||||
}
|
||||
dstSb.append(dst);
|
||||
}
|
||||
} else {
|
||||
sb.append("-");
|
||||
dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ? activityCallback.getWalletAddress() : "-");
|
||||
}
|
||||
tvTxTransfers.setText(sb.toString());
|
||||
tvDestination.setText(dstSb.toString());
|
||||
this.info = info;
|
||||
bCopy.setEnabled(true);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -16,17 +16,18 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.Fragment;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.constraint.ConstraintLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.DividerItemDecoration;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
@@ -36,9 +37,8 @@ import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
|
||||
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||
import com.m2049r.xmrwallet.model.Transfer;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.util.List;
|
||||
@@ -49,14 +49,27 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
private NumberFormat formatter = NumberFormat.getInstance();
|
||||
|
||||
TextView tvBalance;
|
||||
TextView tvUnlockedBalance;
|
||||
LinearLayout llUnconfirmedAmount;
|
||||
TextView tvUnconfirmedAmount;
|
||||
TextView tvBlockHeightProgress;
|
||||
TextView tvConnectionStatus;
|
||||
LinearLayout llProgress;
|
||||
ConstraintLayout clProgress;
|
||||
TextView tvProgress;
|
||||
ProgressBar pbProgress;
|
||||
Button bSend;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if (activityCallback.hasWallet())
|
||||
inflater.inflate(R.menu.wallet_menu, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -64,11 +77,13 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
|
||||
tvProgress = (TextView) view.findViewById(R.id.tvProgress);
|
||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
||||
llProgress = (LinearLayout) view.findViewById(R.id.llProgress);
|
||||
clProgress = (ConstraintLayout) view.findViewById(R.id.clProgress);
|
||||
llUnconfirmedAmount = (LinearLayout) view.findViewById(R.id.llUnconfirmedAmount);
|
||||
tvBalance = (TextView) view.findViewById(R.id.tvBalance);
|
||||
tvUnlockedBalance = (TextView) view.findViewById(R.id.tvUnlockedBalance);
|
||||
tvBalance.setText(Helper.getDisplayAmount(0));
|
||||
tvUnconfirmedAmount = (TextView) view.findViewById(R.id.tvUnconfirmedAmount);
|
||||
tvUnconfirmedAmount.setText(Helper.getDisplayAmount(0));
|
||||
tvBlockHeightProgress = (TextView) view.findViewById(R.id.tvBlockHeightProgress);
|
||||
tvConnectionStatus = (TextView) view.findViewById(R.id.tvConnectionStatus);
|
||||
|
||||
bSend = (Button) view.findViewById(R.id.bSend);
|
||||
|
||||
@@ -118,7 +133,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
}
|
||||
|
||||
public void onSynced() {
|
||||
if (!activityCallback.isWatchOnly() && WalletManager.getInstance().isTestNet()) {
|
||||
if (!activityCallback.isWatchOnly()) {
|
||||
bSend.setVisibility(View.VISIBLE);
|
||||
bSend.setEnabled(true);
|
||||
}
|
||||
@@ -145,11 +160,11 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
}
|
||||
|
||||
public void showProgress() {
|
||||
llProgress.setVisibility(View.VISIBLE);
|
||||
clProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
public void hideProgress() {
|
||||
llProgress.setVisibility(View.GONE);
|
||||
clProgress.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
String setActivityTitle(Wallet wallet) {
|
||||
@@ -158,10 +173,12 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
if (shortName.length() > 16) {
|
||||
shortName = shortName.substring(0, 14) + "...";
|
||||
}
|
||||
String title = "[" + wallet.getAddress().substring(0, 6) + "] "
|
||||
+ shortName
|
||||
+ (wallet.isWatchOnly() ? " " + getString(R.string.watchonly_label) : "");
|
||||
String title = "[" + wallet.getAddress().substring(0, 6) + "] " + shortName;
|
||||
activityCallback.setTitle(title);
|
||||
|
||||
String watchOnly = (wallet.isWatchOnly() ? " " + getString(R.string.watchonly_label) : "");
|
||||
String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
|
||||
activityCallback.setSubtitle(net + " " + watchOnly);
|
||||
Log.d(TAG, "wallet title is " + title);
|
||||
return title;
|
||||
}
|
||||
@@ -175,8 +192,16 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
walletTitle = setActivityTitle(wallet);
|
||||
onProgress(100); // of loading
|
||||
}
|
||||
tvBalance.setText(Wallet.getDisplayAmount(wallet.getBalance()));
|
||||
tvUnlockedBalance.setText(Wallet.getDisplayAmount(wallet.getUnlockedBalance()));
|
||||
long balance = wallet.getBalance();
|
||||
long unlockedBalance = wallet.getUnlockedBalance();
|
||||
tvBalance.setText(Helper.getDisplayAmount(unlockedBalance));
|
||||
tvUnconfirmedAmount.setText(Helper.getDisplayAmount(balance - unlockedBalance));
|
||||
// balance cannot be less than unlockedBalance
|
||||
/*if (balance != unlockedBalance) {
|
||||
llPendingAmount.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
llPendingAmount.setVisibility(View.INVISIBLE);
|
||||
}*/
|
||||
String sync = "";
|
||||
if (!activityCallback.hasBoundService())
|
||||
throw new IllegalStateException("WalletService not bound.");
|
||||
@@ -197,9 +222,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight());
|
||||
}
|
||||
}
|
||||
String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
|
||||
tvBlockHeightProgress.setText(sync);
|
||||
tvConnectionStatus.setText(net + " " + daemonConnected.toString().substring(17));
|
||||
//String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
|
||||
//activityCallback.setSubtitle(net + " " + daemonConnected.toString().substring(17));
|
||||
// TODO show connected status somewhere
|
||||
}
|
||||
|
||||
Listener activityCallback;
|
||||
@@ -216,6 +242,8 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
|
||||
void setTitle(String title);
|
||||
|
||||
void setSubtitle(String subtitle);
|
||||
|
||||
void onSendRequest();
|
||||
|
||||
void onTxDetailsRequest(TransactionInfo info);
|
||||
@@ -225,6 +253,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
|
||||
boolean isWatchOnly();
|
||||
|
||||
String getTxKey(String txId);
|
||||
|
||||
void onWalletReceive();
|
||||
|
||||
boolean hasWallet();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -40,8 +40,7 @@ import java.util.TimeZone;
|
||||
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
|
||||
private static final String TAG = "TransactionInfoAdapter";
|
||||
|
||||
private final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
|
||||
private final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
|
||||
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||
|
||||
static final int TX_RED = Color.rgb(255, 79, 65);
|
||||
static final int TX_GREEN = Color.rgb(54, 176, 91);
|
||||
@@ -60,8 +59,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
this.listener = listener;
|
||||
Calendar cal = Calendar.getInstance();
|
||||
TimeZone tz = cal.getTimeZone(); //get the local time zone.
|
||||
DATE_FORMATTER.setTimeZone(tz);
|
||||
TIME_FORMATTER.setTimeZone(tz);
|
||||
DATETIME_FORMATTER.setTimeZone(tz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,16 +89,15 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
Collections.sort(data, new Comparator<TransactionInfo>() {
|
||||
@Override
|
||||
public int compare(TransactionInfo o1, TransactionInfo o2) {
|
||||
if ((o1.isPending) && (o2.isPending)) {
|
||||
long b1 = o1.timestamp;
|
||||
long b2 = o2.timestamp;
|
||||
return (b1 > b2) ? -1 : (b1 < b2) ? 1 : 0;
|
||||
long b1 = o1.timestamp;
|
||||
long b2 = o2.timestamp;
|
||||
if (b1 > b2) {
|
||||
return -1;
|
||||
} else if (b1 < b2) {
|
||||
return 1;
|
||||
} else {
|
||||
return o1.hash.compareTo(o2.hash);
|
||||
}
|
||||
if (o1.isPending) return -1;
|
||||
if (o2.isPending) return 1;
|
||||
long b1 = o1.blockheight;
|
||||
long b2 = o2.blockheight;
|
||||
return (b1 > b2) ? -1 : (b1 < b2) ? 1 : 0;
|
||||
}
|
||||
});
|
||||
this.infoItems.addAll(data);
|
||||
@@ -114,8 +111,8 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
final TextView tvAmount;
|
||||
final TextView tvAmountPoint;
|
||||
final TextView tvAmountDecimal;
|
||||
final TextView tvDate;
|
||||
final TextView tvTime;
|
||||
final TextView tvPaymentId;
|
||||
final TextView tvDateTime;
|
||||
TransactionInfo infoItem;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
@@ -124,16 +121,12 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
// I know this is stupid but can't be bothered to align decimals otherwise
|
||||
this.tvAmountPoint = (TextView) itemView.findViewById(R.id.tx_amount_point);
|
||||
this.tvAmountDecimal = (TextView) itemView.findViewById(R.id.tx_amount_decimal);
|
||||
this.tvDate = (TextView) itemView.findViewById(R.id.tx_date);
|
||||
this.tvTime = (TextView) itemView.findViewById(R.id.tx_time);
|
||||
this.tvPaymentId = (TextView) itemView.findViewById(R.id.tx_paymentid);
|
||||
this.tvDateTime = (TextView) itemView.findViewById(R.id.tx_datetime);
|
||||
}
|
||||
|
||||
private String getDate(long time) {
|
||||
return DATE_FORMATTER.format(new Date(time * 1000));
|
||||
}
|
||||
|
||||
private String getTime(long time) {
|
||||
return TIME_FORMATTER.format(new Date(time * 1000));
|
||||
private String getDateTime(long time) {
|
||||
return DATETIME_FORMATTER.format(new Date(time * 1000));
|
||||
}
|
||||
|
||||
private void setTxColour(int clr) {
|
||||
@@ -147,25 +140,26 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
String displayAmount = Wallet.getDisplayAmount(infoItem.amount);
|
||||
// TODO fix this with i8n code but cryptonote::print_money always uses '.' for decimal point
|
||||
String amountParts[] = displayAmount.split("\\.");
|
||||
amountParts[1] = amountParts[1].substring(0,5);
|
||||
|
||||
this.tvAmount.setText(amountParts[0]);
|
||||
this.tvAmountDecimal.setText(amountParts[1]);
|
||||
if (infoItem.isPending) {
|
||||
if (infoItem.isFailed) {
|
||||
this.tvAmount.setText('(' + amountParts[0]);
|
||||
this.tvAmountDecimal.setText(amountParts[1] + ')');
|
||||
setTxColour(TX_FAILED);
|
||||
} else if (infoItem.isPending) {
|
||||
setTxColour(TX_PENDING);
|
||||
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
|
||||
this.tvAmount.setText('-' + amountParts[0]);
|
||||
}
|
||||
} else if (infoItem.isFailed) {
|
||||
this.tvAmount.setText('(' + amountParts[0]);
|
||||
this.tvAmountDecimal.setText(amountParts[1] + ')');
|
||||
setTxColour(TX_FAILED);
|
||||
} else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
|
||||
setTxColour(TX_GREEN);
|
||||
} else {
|
||||
setTxColour(TX_RED);
|
||||
}
|
||||
this.tvDate.setText(getDate(infoItem.timestamp));
|
||||
this.tvTime.setText(getTime(infoItem.timestamp));
|
||||
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000")?"":infoItem.paymentId);
|
||||
this.tvDateTime.setText(getDateTime(infoItem.timestamp));
|
||||
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
@@ -96,10 +96,13 @@ public class Wallet {
|
||||
|
||||
// virtual std::string keysFilename() const = 0;
|
||||
public boolean init(long upper_transaction_size_limit) {
|
||||
return initJ(WalletManager.getInstance().getDaemonAddress(), upper_transaction_size_limit);
|
||||
return initJ(WalletManager.getInstance().getDaemonAddress(), upper_transaction_size_limit,
|
||||
WalletManager.getInstance().getDaemonUsername(),
|
||||
WalletManager.getInstance().getDaemonPassword());
|
||||
}
|
||||
|
||||
private native boolean initJ(String daemon_address, long upper_transaction_size_limit);
|
||||
private native boolean initJ(String daemon_address, long upper_transaction_size_limit,
|
||||
String daemon_username, String daemon_password);
|
||||
|
||||
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
|
||||
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
|
||||
@@ -179,14 +182,15 @@ public class Wallet {
|
||||
long amount, int mixin_count,
|
||||
PendingTransaction.Priority priority) {
|
||||
disposePendingTransaction();
|
||||
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, priority);
|
||||
int _priority = priority.getValue();
|
||||
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority);
|
||||
pendingTransaction = new PendingTransaction(txHandle);
|
||||
return pendingTransaction;
|
||||
}
|
||||
|
||||
private native long createTransactionJ(String dst_addr, String payment_id,
|
||||
long mount, int mixin_count,
|
||||
PendingTransaction.Priority priority);
|
||||
long amount, int mixin_count,
|
||||
int priority);
|
||||
|
||||
|
||||
public PendingTransaction createSweepUnmixableTransaction() {
|
||||
@@ -234,6 +238,7 @@ public class Wallet {
|
||||
public native void setDefaultMixin(int mixin);
|
||||
|
||||
public native boolean setUserNote(String txid, String note);
|
||||
|
||||
public native String getUserNote(String txid);
|
||||
|
||||
public native String getTxKey(String txid);
|
||||
|
@@ -71,8 +71,8 @@ public class WalletManager {
|
||||
managedWallets.remove(walletId);
|
||||
}
|
||||
|
||||
public Wallet createWallet(String path, String password, String language) {
|
||||
long walletHandle = createWalletJ(path, password, language, isTestNet());
|
||||
public Wallet createWallet(File aFile, String password, String language) {
|
||||
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, isTestNet());
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet.getName(), wallet);
|
||||
return wallet;
|
||||
@@ -89,14 +89,14 @@ public class WalletManager {
|
||||
|
||||
private native long openWalletJ(String path, String password, boolean isTestNet);
|
||||
|
||||
public Wallet recoveryWallet(String path, String mnemonic) {
|
||||
Wallet wallet = recoveryWallet(path, mnemonic, 0);
|
||||
public Wallet recoveryWallet(File aFile, String mnemonic) {
|
||||
Wallet wallet = recoveryWallet(aFile, mnemonic, 0);
|
||||
manageWallet(wallet.getName(), wallet);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
public Wallet recoveryWallet(String path, String mnemonic, long restoreHeight) {
|
||||
long walletHandle = recoveryWalletJ(path, mnemonic, isTestNet(), restoreHeight);
|
||||
public Wallet recoveryWallet(File aFile, String mnemonic, long restoreHeight) {
|
||||
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), mnemonic, isTestNet(), restoreHeight);
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet.getName(), wallet);
|
||||
return wallet;
|
||||
@@ -104,9 +104,9 @@ public class WalletManager {
|
||||
|
||||
private native long recoveryWalletJ(String path, String mnemonic, boolean isTestNet, long restoreHeight);
|
||||
|
||||
public Wallet createWalletFromKeys(String path, String language, long restoreHeight,
|
||||
public Wallet createWalletFromKeys(File aFile, String language, long restoreHeight,
|
||||
String addressString, String viewKeyString, String spendKeyString) {
|
||||
long walletHandle = createWalletFromKeysJ(path, language, isTestNet(), restoreHeight,
|
||||
long walletHandle = createWalletFromKeysJ(aFile.getAbsolutePath(), language, isTestNet(), restoreHeight,
|
||||
addressString, viewKeyString, spendKeyString);
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet.getName(), wallet);
|
||||
@@ -134,6 +134,10 @@ public class WalletManager {
|
||||
return closed;
|
||||
}
|
||||
|
||||
public boolean walletExists(File aFile) {
|
||||
return walletExists(aFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
public native boolean walletExists(String path);
|
||||
|
||||
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
|
||||
@@ -146,6 +150,31 @@ public class WalletManager {
|
||||
public String address;
|
||||
}
|
||||
|
||||
public WalletInfo getWalletInfo(File wallet) {
|
||||
WalletInfo info = new WalletInfo();
|
||||
info.path = wallet.getParentFile();
|
||||
info.name = wallet.getName();
|
||||
File addressFile = new File(info.path, info.name + ".address.txt");
|
||||
//Log.d(TAG, addressFile.getAbsolutePath());
|
||||
info.address = "??????";
|
||||
BufferedReader addressReader = null;
|
||||
try {
|
||||
addressReader = new BufferedReader(new FileReader(addressFile));
|
||||
info.address = addressReader.readLine();
|
||||
} catch (IOException ex) {
|
||||
Log.d(TAG, ex.getLocalizedMessage());
|
||||
} finally {
|
||||
if (addressReader != null) {
|
||||
try {
|
||||
addressReader.close();
|
||||
} catch (IOException ex) {
|
||||
// that's just too bad
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
public List<WalletInfo> findWallets(File path) {
|
||||
List<WalletInfo> wallets = new ArrayList<>();
|
||||
Log.d(TAG, "Scanning: " + path.getAbsolutePath());
|
||||
@@ -155,29 +184,9 @@ public class WalletManager {
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < found.length; i++) {
|
||||
WalletInfo info = new WalletInfo();
|
||||
info.path = path;
|
||||
String filename = found[i].getName();
|
||||
info.name = filename.substring(0, filename.length() - 5); // 5 is length of ".keys"+1
|
||||
File addressFile = new File(path, info.name + ".address.txt");
|
||||
//Log.d(TAG, addressFile.getAbsolutePath());
|
||||
info.address = "??????";
|
||||
BufferedReader addressReader = null;
|
||||
try {
|
||||
addressReader = new BufferedReader(new FileReader(addressFile));
|
||||
info.address = addressReader.readLine();
|
||||
} catch (IOException ex) {
|
||||
Log.d(TAG, ex.getLocalizedMessage());
|
||||
} finally {
|
||||
if (addressReader != null) {
|
||||
try {
|
||||
addressReader.close();
|
||||
} catch (IOException ex) {
|
||||
// that's just too bad
|
||||
}
|
||||
}
|
||||
}
|
||||
wallets.add(info);
|
||||
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
|
||||
wallets.add(getWalletInfo(f));
|
||||
}
|
||||
return wallets;
|
||||
}
|
||||
@@ -197,9 +206,12 @@ public class WalletManager {
|
||||
return testnet;
|
||||
}
|
||||
|
||||
public void setDaemon(String address, boolean testnet) {
|
||||
public void setDaemon(String address, boolean testnet, String username, String password) {
|
||||
//Log.d(TAG, "SETDAEMON " + username + "/" + password + "/" + address);
|
||||
this.daemonAddress = address;
|
||||
this.testnet = testnet;
|
||||
this.daemonUsername = username;
|
||||
this.daemonPassword = password;
|
||||
setDaemonAddressJ(address);
|
||||
}
|
||||
|
||||
@@ -213,6 +225,18 @@ public class WalletManager {
|
||||
|
||||
private native void setDaemonAddressJ(String address);
|
||||
|
||||
String daemonUsername = "";
|
||||
|
||||
public String getDaemonUsername() {
|
||||
return daemonUsername;
|
||||
}
|
||||
|
||||
String daemonPassword = "";
|
||||
|
||||
public String getDaemonPassword() {
|
||||
return daemonPassword;
|
||||
}
|
||||
|
||||
public native int getDaemonVersion();
|
||||
|
||||
public native long getBlockchainHeight();
|
||||
|
@@ -19,10 +19,7 @@ package com.m2049r.xmrwallet.service;
|
||||
import android.app.Notification;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -30,13 +27,11 @@ import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Process;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.WalletActivity;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.model.TransactionHistory;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletListener;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
@@ -44,6 +39,8 @@ import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.TxData;
|
||||
|
||||
public class WalletService extends Service {
|
||||
public static boolean Running = false;
|
||||
|
||||
final static String TAG = "WalletService";
|
||||
final static int NOTIFICATION_ID = 2049;
|
||||
|
||||
@@ -127,6 +124,7 @@ public class WalletService extends Service {
|
||||
boolean fullRefresh = false;
|
||||
updateDaemonState(wallet, wallet.isSynchronized() ? height : 0);
|
||||
if (!wallet.isSynchronized()) {
|
||||
updated = true;
|
||||
// we want to see our transactions as they come in
|
||||
wallet.getHistory().refresh();
|
||||
int txCount = wallet.getHistory().getCount();
|
||||
@@ -224,6 +222,8 @@ public class WalletService extends Service {
|
||||
void onSentTransaction(boolean success);
|
||||
|
||||
void onSetNotes(boolean success);
|
||||
|
||||
void onWalletStarted(boolean success);
|
||||
}
|
||||
|
||||
String progressText = null;
|
||||
@@ -263,6 +263,8 @@ public class WalletService extends Service {
|
||||
private Looper mServiceLooper;
|
||||
private WalletService.ServiceHandler mServiceHandler;
|
||||
|
||||
private boolean errorState = false;
|
||||
|
||||
// Handler that receives messages from the thread
|
||||
private final class ServiceHandler extends Handler {
|
||||
ServiceHandler(Looper looper) {
|
||||
@@ -272,6 +274,11 @@ public class WalletService extends Service {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
Log.d(TAG, "Handling " + msg.arg2);
|
||||
if (errorState) {
|
||||
Log.i(TAG, "In error state.");
|
||||
// also, we have already stopped ourselves
|
||||
return;
|
||||
}
|
||||
switch (msg.arg2) {
|
||||
case START_SERVICE: {
|
||||
Bundle extras = msg.getData();
|
||||
@@ -283,7 +290,12 @@ public class WalletService extends Service {
|
||||
if (walletId != null) {
|
||||
showProgress(getString(R.string.status_wallet_loading));
|
||||
showProgress(10);
|
||||
start(walletId, walletPw); // TODO What if this fails?
|
||||
boolean success = start(walletId, walletPw);
|
||||
if (observer != null) observer.onWalletStarted(success);
|
||||
if (!success) {
|
||||
errorState = true;
|
||||
stop();
|
||||
}
|
||||
}
|
||||
} else if (cmd.equals(REQUEST_CMD_STORE)) {
|
||||
Wallet myWallet = getWallet();
|
||||
@@ -327,10 +339,6 @@ public class WalletService extends Service {
|
||||
} else if (cmd.equals(REQUEST_CMD_SEND)) {
|
||||
Wallet myWallet = getWallet();
|
||||
Log.d(TAG, "SEND TX for wallet: " + myWallet.getName());
|
||||
if (!myWallet.isTestNet()) {
|
||||
Log.e(TAG, "Sending transactions only on testnet");
|
||||
throw new IllegalStateException("Sending transactions only in testnet");
|
||||
}
|
||||
PendingTransaction pendingTransaction = myWallet.getPendingTransaction();
|
||||
if (pendingTransaction.getStatus() != PendingTransaction.Status.Status_Ok) {
|
||||
Log.e(TAG, "PendingTransaction is " + pendingTransaction.getStatus());
|
||||
@@ -389,9 +397,6 @@ public class WalletService extends Service {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
//mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
//showNotification();
|
||||
|
||||
// We are using a HandlerThread and a Looper to avoid loading and closing
|
||||
// concurrency
|
||||
MoneroHandlerThread thread = new MoneroHandlerThread("WalletService",
|
||||
@@ -408,8 +413,6 @@ public class WalletService extends Service {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
Log.d(TAG, "onDestroy()");
|
||||
// Cancel the persistent notification.
|
||||
//mNM.cancel(NOTIFICATION);
|
||||
if (this.listener != null) {
|
||||
Log.w(TAG, "onDestroy() with active listener");
|
||||
// no need to stop() here because the wallet closing should have been triggered
|
||||
@@ -427,7 +430,8 @@ public class WalletService extends Service {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
// when the activity satrts the service, it expects to start it for a new wallet
|
||||
Running = true;
|
||||
// when the activity starts the service, it expects to start it for a new wallet
|
||||
// the service is possibly still occupied with saving the last opened wallet
|
||||
// so we queue the open request
|
||||
// this should not matter since the old activity is not getting updates
|
||||
@@ -461,7 +465,7 @@ public class WalletService extends Service {
|
||||
return true; // true is important so that onUnbind is also called next time
|
||||
}
|
||||
|
||||
private void start(String walletName, String walletPassword) {
|
||||
private boolean start(String walletName, String walletPassword) {
|
||||
startNotfication();
|
||||
// if there is an listener it is always started / syncing
|
||||
Log.d(TAG, "start()");
|
||||
@@ -470,7 +474,10 @@ public class WalletService extends Service {
|
||||
if (listener == null) {
|
||||
Log.d(TAG, "start() loadWallet");
|
||||
Wallet aWallet = loadWallet(walletName, walletPassword);
|
||||
// TODO check aWallet and die gracefully if neccessary
|
||||
if ((aWallet == null) || (aWallet.getConnectionStatus() != Wallet.ConnectionStatus.ConnectionStatus_Connected)) {
|
||||
if (aWallet != null) aWallet.close();
|
||||
return false;
|
||||
}
|
||||
listener = new MyWalletListener(aWallet);
|
||||
listener.start();
|
||||
showProgress(100);
|
||||
@@ -480,6 +487,7 @@ public class WalletService extends Service {
|
||||
// if we try to refresh the history here we get occasional segfaults!
|
||||
// doesnt matter since we update as soon as we get a new block anyway
|
||||
Log.d(TAG, "start() done");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
@@ -499,6 +507,7 @@ public class WalletService extends Service {
|
||||
}
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
Running = false;
|
||||
}
|
||||
|
||||
private Wallet loadWallet(String walletName, String walletPassword) {
|
||||
@@ -512,13 +521,13 @@ public class WalletService extends Service {
|
||||
showProgress(55);
|
||||
wallet.init(0);
|
||||
showProgress(90);
|
||||
Log.d(TAG, wallet.getConnectionStatus().toString());
|
||||
//Log.d(TAG, wallet.getConnectionStatus().toString());
|
||||
}
|
||||
return wallet;
|
||||
}
|
||||
|
||||
private Wallet openWallet(String walletName, String walletPassword) {
|
||||
String path = Helper.getWalletPath(getApplicationContext(), walletName);
|
||||
String path = Helper.getWalletFile(getApplicationContext(), walletName).getAbsolutePath();
|
||||
showProgress(20);
|
||||
Wallet wallet = null;
|
||||
WalletManager walletMgr = WalletManager.getInstance();
|
||||
@@ -548,7 +557,7 @@ public class WalletService extends Service {
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
|
||||
Notification notification = new Notification.Builder(this)
|
||||
.setContentTitle(getString(R.string.service_description))
|
||||
.setSmallIcon(R.drawable.ic_notification_sync_32_32)
|
||||
.setSmallIcon(R.drawable.ic_monero_32dp)
|
||||
.setContentIntent(pendingIntent)
|
||||
.build();
|
||||
startForeground(NOTIFICATION_ID, notification);
|
||||
|
29
app/src/main/java/com/m2049r/xmrwallet/util/BarcodeData.java
Normal file
29
app/src/main/java/com/m2049r/xmrwallet/util/BarcodeData.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.util;
|
||||
|
||||
public class BarcodeData {
|
||||
public String address = null;
|
||||
public String paymentId = null;
|
||||
public long amount = -1;
|
||||
|
||||
public BarcodeData(String address, String paymentId, long amount) {
|
||||
this.address = address;
|
||||
this.paymentId = paymentId;
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
@@ -21,12 +21,21 @@ import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -71,12 +80,34 @@ public class Helper {
|
||||
}
|
||||
}
|
||||
|
||||
static public String getWalletPath(Context context, String aWalletName) {
|
||||
public static final int PERMISSIONS_REQUEST_CAMERA = 1;
|
||||
|
||||
static public boolean getCameraPermission(Activity context) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
if (context.checkSelfPermission(Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_DENIED) {
|
||||
Log.d(TAG, "Permission denied for CAMERA - requesting it");
|
||||
String[] permissions = {Manifest.permission.CAMERA};
|
||||
context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// static public String getWalletPath(Context context, String aWalletName) {
|
||||
// return getWalletFile(context, aWalletName).getAbsolutePath();
|
||||
// }
|
||||
|
||||
static public File getWalletFile(Context context, String aWalletName) {
|
||||
File walletDir = getStorageRoot(context);
|
||||
//d(TAG, "walletdir=" + walletDir.getAbsolutePath());
|
||||
File f = new File(walletDir, aWalletName);
|
||||
Log.d(TAG, "wallet = " + f.getAbsolutePath() + " size=" + f.length());
|
||||
return f.getAbsolutePath();
|
||||
return f;
|
||||
}
|
||||
|
||||
/* Checks if external storage is available for read and write */
|
||||
@@ -107,4 +138,51 @@ public class Helper {
|
||||
static public void hideKeyboardAlways(Activity act) {
|
||||
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
||||
}
|
||||
|
||||
static public boolean isAddressOk(String address, boolean testnet) {
|
||||
if (address == null) return false;
|
||||
if (testnet) {
|
||||
return ((address.length() == 95) && ("9A".indexOf(address.charAt(0)) >= 0));
|
||||
} else {
|
||||
return ((address.length() == 95) && ("4".indexOf(address.charAt(0)) >= 0));
|
||||
}
|
||||
}
|
||||
|
||||
static public String getDisplayAmount(long amount) {
|
||||
String s = Wallet.getDisplayAmount(amount);
|
||||
int lastZero = 0;
|
||||
int decimal = 0;
|
||||
for (int i = s.length() - 1; i >= 0; i--) {
|
||||
if ((lastZero == 0) && (s.charAt(i) != '0')) lastZero = i + 1;
|
||||
// TODO i18n
|
||||
if (s.charAt(i) == '.') {
|
||||
decimal = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Log.d(TAG, decimal + "/" + lastZero + "/" + s);
|
||||
int cutoff = Math.max(lastZero, decimal + 2);
|
||||
return s.substring(0, cutoff);
|
||||
}
|
||||
|
||||
public static Bitmap getBitmap(Context context, int drawableId) {
|
||||
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
return BitmapFactory.decodeResource(context.getResources(), drawableId);
|
||||
} else if (drawable instanceof VectorDrawable) {
|
||||
return getBitmap((VectorDrawable) drawable);
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported drawable type");
|
||||
}
|
||||
}
|
||||
|
||||
private static Bitmap getBitmap(VectorDrawable vectorDrawable) {
|
||||
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
|
||||
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||
vectorDrawable.draw(canvas);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2017 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.util;
|
||||
|
||||
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
|
||||
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
public class MoneroThreadPoolExecutor {
|
||||
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
|
||||
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
|
||||
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
|
||||
private static final int KEEP_ALIVE_SECONDS = 30;
|
||||
|
||||
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
|
||||
private final AtomicInteger mCount = new AtomicInteger(1);
|
||||
|
||||
public Thread newThread(Runnable r) {
|
||||
return new Thread(null, r, "MoneroTask #" + mCount.getAndIncrement(), MoneroHandlerThread.THREAD_STACK_SIZE);
|
||||
}
|
||||
};
|
||||
|
||||
private static final BlockingQueue<Runnable> sPoolWorkQueue =
|
||||
new LinkedBlockingQueue<>(128);
|
||||
|
||||
public static final Executor MONERO_THREAD_POOL_EXECUTOR;
|
||||
|
||||
static {
|
||||
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
|
||||
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
|
||||
sPoolWorkQueue, sThreadFactory);
|
||||
threadPoolExecutor.allowCoreThreadTimeOut(true);
|
||||
MONERO_THREAD_POOL_EXECUTOR = threadPoolExecutor;
|
||||
}
|
||||
}
|
@@ -75,4 +75,19 @@ public class TxData implements Parcelable {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("dst_addr:");
|
||||
sb.append(dst_addr);
|
||||
sb.append(",paymentId:");
|
||||
sb.append(paymentId);
|
||||
sb.append(",amount:");
|
||||
sb.append(amount);
|
||||
sb.append(",mixin:");
|
||||
sb.append(mixin);
|
||||
sb.append(",priority:");
|
||||
sb.append(priority.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
@@ -5,5 +5,5 @@
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
14
app/src/main/res/drawable/ic_monero_32dp.xml
Normal file
14
app/src/main/res/drawable/ic_monero_32dp.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportHeight="75.0"
|
||||
android:viewportWidth="75.0">
|
||||
|
||||
<path
|
||||
android:fillColor="#ff6600"
|
||||
android:pathData="M 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" />
|
||||
<path
|
||||
android:fillColor="#4c4c4c"
|
||||
android:pathData="M 21.3164,36.895994 l 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" />
|
||||
|
||||
</vector>
|
21
app/src/main/res/drawable/ic_monero_qr.xml
Normal file
21
app/src/main/res/drawable/ic_monero_qr.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportHeight="75.0"
|
||||
android:viewportWidth="75.0">
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData=" M 37.3, 37.3
|
||||
m -36.9, 0
|
||||
a 36.9,36.9 0 1,0 73.8,0
|
||||
a 36.9,36.9 0 1,0 -73.8,0" />
|
||||
|
||||
<path
|
||||
android:fillColor="#ff6600"
|
||||
android:pathData="M 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" />
|
||||
<path
|
||||
android:fillColor="#4c4c4c"
|
||||
android:pathData="M 21.3164,36.895994 l 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" />
|
||||
|
||||
</vector>
|
33
app/src/main/res/drawable/ic_monero_qr_24dp.xml
Normal file
33
app/src/main/res/drawable/ic_monero_qr_24dp.xml
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
|
||||
</vector>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user