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

Compare commits

...

25 Commits

Author SHA1 Message Date
m2049r
e9c74d4d9c Random fixes (#228)
* adapt for android studio 3.1 and remove witness

* set networktype from node

* add monero logging for DEBUG builds

* do not reset timestamps in apk

* no witness

* new version & apk naming
2018-04-15 16:33:20 +02:00
m2049r
b37adb4546 Update README.md 2018-04-11 18:20:07 +02:00
erciccione
a4e99209be change suggested ringsize from 5 to 7 in help.xml (#223) 2018-04-07 16:44:18 +02:00
m2049r
45aa8f30e5 fix openssl links 2018-04-05 19:09:30 +02:00
m2049r
60a7b15700 new version for fixed malloc (83dcd65) 2018-04-03 11:51:14 +02:00
m2049r
df2ff8b3b7 correct toolbar colour for testnet (#218) 2018-04-02 13:02:00 +02:00
m2049r
da8c8f80f1 new version for m2049r/monero 7e97e11 (#217) 2018-04-02 12:14:55 +02:00
m2049r
eda3de11fe added zxcvbn4j license (#216) 2018-04-01 13:23:03 +02:00
m2049r
7d7de14827 new version id for monero PR#3526 (#215) 2018-03-30 14:12:17 +02:00
m2049r
1c709da92c Update FAQ.md 2018-03-29 23:00:39 +02:00
m2049r
a9092497b2 changes for monero v0.12 (#214)
* new version id & name
* witness checksums
* build docs updated for v0.12
* remove binaries
* setenv HOME for ringdb to 'monero' in shared storage
* min ringsize 7
* remove boost_locale and zmq from build - don't need them for wallet_api
* splits for all archs
* throw IndexOutOfBounds in case the TX is empty
* donate, you ungrateful bastards! (removed donations to make google happy)
2018-03-29 22:35:31 +02:00
m2049r
b1f530e64a new version number (#208) 2018-03-13 22:31:32 +01:00
m2049r
8c086b77d3 Minor Bugfixes (#207)
* corrected spanish

* removed testcode

* fix issue #206
2018-03-06 18:15:23 +01:00
m2049r
c0fdfe87be build for x86 & arm in both 32 & 64 bits (#204) 2018-02-27 07:22:11 +01:00
m2049r
6cfd840283 fixed some bugs & reverted to boost 1.58 2018-02-26 09:46:20 +01:00
Miguel Botón
d80cde1136 Added build instructions for 64-bit (aarch64). (#172) 2018-02-26 08:42:22 +01:00
m2049r
bea4b06675 new version 2018-02-23 19:47:20 +01:00
m2049r
88bc33b5a4 added zxcvbn password score (#199)
* added zxcvbn password score

* changes messages & thresholds

change password label to passphrase
2018-02-23 19:32:21 +01:00
m2049r
2db36bb824 Enable Restore with Date instead of Height Only (#198)
* restore height class

* exact heights

* tweaks with DST

* some more spanish
2018-02-23 19:31:55 +01:00
m2049r
dd689b1883 fix for 2N9fzq66uZYQXp7uqrPBH6jKBhjrgTzpGCy (#190) 2018-01-24 19:59:13 +01:00
m2049r
641abd13f5 split apk (#191) 2018-01-23 21:35:26 +01:00
Stephan Hagios
a0b3a7fe5d used background borderless for nicer ripple effect (#189) 2018-01-22 21:36:13 +01:00
Stephan Hagios
a0486f581f Added circleci yml (#188)
* Adjusted layout files with correct sizes.

* Added password visibility toggle

* PR suggestions

* circle ci yml added

* different container with ndk
2018-01-21 18:53:45 +01:00
m2049r
36161137ec correct spanish (#187) 2018-01-18 17:45:46 +01:00
m2049r
c9927edbd1 save on exit only with connected wallet (#186) 2018-01-18 17:23:35 +01:00
94 changed files with 1589 additions and 652 deletions

27
.circleci/config.yml Normal file
View File

@@ -0,0 +1,27 @@
version: 2
jobs:
build:
working_directory: ~/code
docker:
- image: bitriseio/android-ndk
environment:
JVM_OPTS: -Xmx3200m
steps:
- checkout
- restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:
name: Download Dependencies
command: ./gradlew androidDependencies
- save_cache:
paths:
- ~/.gradle
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run:
name: Run Tests
command: ./gradlew test
- store_artifacts:
path: app/build/reports
destination: reports
- store_test_results:
path: app/build/test-results

2
.gitignore vendored
View File

@@ -3,6 +3,8 @@
*.iml *.iml
/.idea/libraries /.idea/libraries
/.idea/workspace.xml /.idea/workspace.xml
/.idea/caches
/.idea/codeStyles
/local.properties /local.properties
/captures /captures
.externalNativeBuild .externalNativeBuild

View File

@@ -3,6 +3,7 @@ Another Android Monero Wallet
### QUICKSTART ### QUICKSTART
- Download the APK for the most current release [here](https://github.com/m2049r/xmrwallet/releases) and install it - Download the APK for the most current release [here](https://github.com/m2049r/xmrwallet/releases) and install it
- Alternatively add our F-Droid repo https://f-droid.monerujo.io/fdroid/repo with fingerpint ```A8 2C 68 E1 4A F0 AA 6A 2E C2 0E 6B 27 2E FF 25 E5 A0 38 F3 F6 58 84 31 6E 0F 5E 0D 91 E7 B7 13``` to your F-Droid client
- Run the App and select "Generate Wallet" to create a new wallet or recover a wallet - Run the App and select "Generate Wallet" to create a new wallet or recover a wallet
- Advanced users can copy over synced wallet files (all files) onto sdcard in directory Monerujo (created first time App is started) - Advanced users can copy over synced wallet files (all files) onto sdcard in directory Monerujo (created first time App is started)
- See the [FAQ](doc/FAQ.md) - See the [FAQ](doc/FAQ.md)

View File

@@ -60,9 +60,13 @@ set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_wserialization.a) ${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_wserialization.a)
############# #############
# Monero set(libs_to_merge wallet cryptonote_core cryptonote_basic mnemonics common cncrypto ringct) # Monero
############# #############
add_library(wallet_api STATIC IMPORTED)
set_target_properties(wallet_api PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet_api.a)
add_library(wallet STATIC IMPORTED) add_library(wallet STATIC IMPORTED)
set_target_properties(wallet PROPERTIES IMPORTED_LOCATION set_target_properties(wallet PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet.a) ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet.a)
@@ -91,11 +95,9 @@ add_library(ringct STATIC IMPORTED)
set_target_properties(ringct PROPERTIES IMPORTED_LOCATION set_target_properties(ringct PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct.a) ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct.a)
##### add_library(ringct_basic STATIC IMPORTED)
set_target_properties(ringct_basic PROPERTIES IMPORTED_LOCATION
add_library(p2p STATIC IMPORTED) ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct_basic.a)
set_target_properties(p2p PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libp2p.a)
add_library(blockchain_db STATIC IMPORTED) add_library(blockchain_db STATIC IMPORTED)
set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION
@@ -113,7 +115,6 @@ add_library(unbound STATIC IMPORTED)
set_target_properties(unbound PROPERTIES IMPORTED_LOCATION set_target_properties(unbound PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libunbound.a) ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libunbound.a)
####
add_library(epee STATIC IMPORTED) add_library(epee STATIC IMPORTED)
set_target_properties(epee PROPERTIES IMPORTED_LOCATION set_target_properties(epee PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libepee.a) ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libepee.a)
@@ -122,9 +123,21 @@ add_library(blocks STATIC IMPORTED)
set_target_properties(blocks PROPERTIES IMPORTED_LOCATION set_target_properties(blocks PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblocks.a) ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblocks.a)
add_library(miniupnpc STATIC IMPORTED) add_library(checkpoints STATIC IMPORTED)
set_target_properties(miniupnpc PROPERTIES IMPORTED_LOCATION set_target_properties(checkpoints PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libminiupnpc.a) ${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcheckpoints.a)
add_library(device STATIC IMPORTED)
set_target_properties(device PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice.a)
add_library(multisig STATIC IMPORTED)
set_target_properties(multisig PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmultisig.a)
add_library(version STATIC IMPORTED)
set_target_properties(version PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libversion.a)
############# #############
# System # System
@@ -137,23 +150,26 @@ include_directories( ${EXTERNAL_LIBS_DIR}/monero/include )
message(STATUS EXTERNAL_LIBS_DIR : ${EXTERNAL_LIBS_DIR}) message(STATUS EXTERNAL_LIBS_DIR : ${EXTERNAL_LIBS_DIR})
target_link_libraries( monerujo target_link_libraries( monerujo
wallet_api
wallet wallet
cryptonote_core cryptonote_core
cryptonote_basic cryptonote_basic
mnemonics mnemonics
ringct ringct
ringct_basic
common common
cncrypto cncrypto
blockchain_db blockchain_db
lmdb lmdb
easylogging easylogging
unbound unbound
p2p
epee epee
blocks blocks
miniupnpc checkpoints
device
multisig
version
boost_chrono boost_chrono
boost_date_time boost_date_time

View File

@@ -1,15 +1,14 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'witness'
android { android {
compileSdkVersion 25 compileSdkVersion 25
buildToolsVersion '26.0.2' buildToolsVersion '27.0.3'
defaultConfig { defaultConfig {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 25 targetSdkVersion 25
versionCode 68 versionCode 87
versionName "1.3.8 'Satoshis Dream'" versionName "1.4.7 'Monero Spedner'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@@ -17,9 +16,6 @@ android {
arguments '-DANDROID_STL=c++_shared' arguments '-DANDROID_STL=c++_shared'
} }
} }
ndk {
abiFilters 'armeabi-v7a'
}
} }
buildTypes { buildTypes {
@@ -36,49 +32,51 @@ android {
path "CMakeLists.txt" path "CMakeLists.txt"
} }
} }
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
universalApk true
}
}
// Map for the version code that gives each ABI a value.
def abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
// APKs for the same app that all have the same version information.
android.applicationVariants.all { variant ->
// Assigns a different version code for each output APK.
variant.outputs.all {
output ->
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode
if (abiName == null) abiName = "universal"
def v = "${variant.versionName}".replaceFirst(" .*\$", "").replace(".", "x")
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
}
}
} }
dependencies { dependencies {
compile 'com.android.support:appcompat-v7:25.4.0' implementation 'com.android.support:appcompat-v7:25.4.0'
compile 'com.android.support:design:25.4.0' implementation 'com.android.support:design:25.4.0'
compile 'com.android.support:support-v4:25.4.0' implementation 'com.android.support:support-v4:25.4.0'
compile 'com.android.support:recyclerview-v7:25.4.0' implementation 'com.android.support:recyclerview-v7:25.4.0'
compile 'com.android.support:cardview-v7:25.4.0' implementation 'com.android.support:cardview-v7:25.4.0'
compile 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
compile "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
compile "com.jakewharton.timber:timber:$rootProject.ext.timberVersion" implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
testCompile "junit:junit:$rootProject.ext.junitVersion" implementation 'com.nulab-inc:zxcvbn:1.2.3'
testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testCompile "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" testImplementation "junit:junit:$rootProject.ext.junitVersion"
testCompile 'org.json:json:20140107' testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testCompile 'net.jodah:concurrentunit:0.4.2' testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
} testImplementation 'org.json:json:20140107'
testImplementation 'net.jodah:concurrentunit:0.4.2'
dependencyVerification {
verify = [
'com.android.support:design:3f409bf2019967ffc344cfaf11e52131fac982468a1707aaeb25bf3c52838966',
'com.android.support:appcompat-v7:70551e62660db15b790c5275f56b9de4dd9407d1494d07c8f3dd5698f3638677',
'com.android.support:transition:848270144fb180efd2bf928a00ed176dbbc5290badfd638390ffba90088df8b3',
'me.dm7.barcodescanner:zxing:d43973c9527c23fa8e6d338c6a2c458e373ce1ac6bcaa3bc41d11ae49116000d',
'me.dm7.barcodescanner:core:a5c8a704089b58029db166172ed8e55d756877d010a85a0b1c94fdc96ffb8f9a',
'com.android.support:support-v4:ee44c481a1f4d6978568e223e8125379b52b2ececdd53450e09ebae144bd377d',
'com.android.support:recyclerview-v7:a2fe121f9d01ed8980e97095b4a3fe9700a0aa0a7d4b0f8c594f765ad8455a0d',
'com.android.support:cardview-v7:f3fbbe1fcfdbec7333c6a2c516c5fd511a909d1975271818e268d6fe297d8c70',
'com.android.support.constraint:constraint-layout:b0c688cc2b7172608f8153a689d746da40f71e52d7e2fe2bfd9df2f92db77085',
'com.android.support:animated-vector-drawable:628ab1d56a6ee4cbedf32617af8b2a1fe02964ed0628e8f898cc09ddba6e1835',
'com.android.support:support-vector-drawable:077009d13882ee96f061e4bc2dbe7cce7ae1762d8297592a787ff741afbfb1f2',
'com.android.support:support-fragment:316d35d4d2d2902057efad104a73e4bdb50bee260a7075678185b8cd71170945',
'com.android.support:support-core-ui:e72ae29b823889686cff6fcb948d6745c2baf6d4c2af4fdffa1ec1e42e3833a3',
'com.android.support:support-media-compat:566a161d9cb0083ef62a53e46b71ce5b3d455b8635b1a0a4ae28d96d4b583de8',
'com.android.support:support-core-utils:34b8437dfa95ff28d29cf57ffa3b1354a9fa9bfe4059f0fd5ce2f5e4326a1748',
'com.android.support:support-compat:54019c63614ce08b02d7b9605490cd2b29ba5b2505f394a9517450b5f72b30ca',
'com.android.support:support-annotations:a774272036941b4e912eb426d70c848bde7f06a3bf5fb491f75a427dc6595270',
'com.android.support.constraint:constraint-layout-solver:8c62525a9bc5cff5633a96cb9b32fffeccaf41b8841aa87fc22607070dea9b8d',
'com.google.zxing:core:bba7724e02a997cec38213af77133ee8e24b0d5cf5fa7ecbc16a4fa93f11ee0d',
'com.squareup.okio:okio:734269c3ebc5090e3b23566db558f421f0b4027277c79ad5d176b8ec168bb850',
'com.squareup.okhttp3:okhttp:7265adbd6f028aade307f58569d814835cd02bc9beffb70c25f72c9de50d61c4',
]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -21,7 +21,9 @@ import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout; import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@@ -32,12 +34,18 @@ import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import com.m2049r.xmrwallet.util.RestoreHeight;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
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.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;
import java.io.File; import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import timber.log.Timber; import timber.log.Timber;
@@ -125,7 +133,7 @@ public class GenerateFragment extends Fragment {
}); });
Helper.showKeyboard(getActivity()); Helper.showKeyboard(getActivity());
//##############
etWalletName.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etWalletName.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
@@ -231,9 +239,7 @@ public class GenerateFragment extends Fragment {
} }
if (!type.equals(TYPE_NEW)) { if (!type.equals(TYPE_NEW)) {
etWalletRestoreHeight.setVisibility(View.VISIBLE); etWalletRestoreHeight.setVisibility(View.VISIBLE);
etWalletRestoreHeight.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() etWalletRestoreHeight.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
{
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@@ -254,11 +260,61 @@ public class GenerateFragment extends Fragment {
} }
}); });
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
checkPassword();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etWalletName.requestFocus(); etWalletName.requestFocus();
initZxcvbn();
return view; return view;
} }
Zxcvbn zxcvbn = new Zxcvbn();
// initialize zxcvbn engine in background thread
private void initZxcvbn() {
new Thread(new Runnable() {
@Override
public void run() {
zxcvbn.measure("");
}
}).start();
}
private void checkPassword() {
String password = etWalletPassword.getEditText().getText().toString();
if (!password.isEmpty()) {
Strength strength = zxcvbn.measure(password);
int msg;
double guessesLog10 = strength.getGuessesLog10();
if (guessesLog10 < 10)
msg = R.string.password_weak;
else if (guessesLog10 < 11)
msg = R.string.password_fair;
else if (guessesLog10 < 12)
msg = R.string.password_good;
else if (guessesLog10 < 13)
msg = R.string.password_strong;
else
msg = R.string.password_very_strong;
etWalletPassword.setError(getResources().getString(msg));
} else {
etWalletPassword.setError(null);
}
}
private boolean checkName() { private boolean checkName() {
String name = etWalletName.getEditText().getText().toString(); String name = etWalletName.getEditText().getText().toString();
boolean ok = true; boolean ok = true;
@@ -281,6 +337,42 @@ public class GenerateFragment extends Fragment {
return ok; return ok;
} }
private boolean checkHeight() {
long height = !type.equals(TYPE_NEW) ? getHeight() : 0;
boolean ok = true;
if (height < 0) {
etWalletRestoreHeight.setError(getString(R.string.generate_restoreheight_error));
ok = false;
}
if (ok) {
etWalletRestoreHeight.setError(null);
}
return ok;
}
private long getHeight() {
long height = 0;
String restoreHeight = etWalletRestoreHeight.getEditText().getText().toString().trim();
if (restoreHeight.isEmpty()) return -1;
try {
// is it a date?
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
parser.setLenient(false);
parser.parse(restoreHeight);
height = RestoreHeight.getInstance().getHeight(restoreHeight);
} catch (ParseException exPE) {
try {
// or is it a height?
height = Long.parseLong(restoreHeight);
} catch (NumberFormatException exNFE) {
return -1;
}
}
Timber.d("Using Restore Height = %d", height);
return height;
}
private boolean checkMnemonic() { private boolean checkMnemonic() {
String seed = etWalletMnemonic.getEditText().getText().toString(); String seed = etWalletMnemonic.getEditText().getText().toString();
boolean ok = (seed.split("\\s").length == 25); // 25 words boolean ok = (seed.split("\\s").length == 25); // 25 words
@@ -294,7 +386,7 @@ public class GenerateFragment extends Fragment {
private boolean checkAddress() { private boolean checkAddress() {
String address = etWalletAddress.getEditText().getText().toString(); String address = etWalletAddress.getEditText().getText().toString();
boolean ok = Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); boolean ok = Wallet.isAddressValid(address);
if (!ok) { if (!ok) {
etWalletAddress.setError(getString(R.string.generate_check_address)); etWalletAddress.setError(getString(R.string.generate_check_address));
} else { } else {
@@ -327,15 +419,13 @@ public class GenerateFragment extends Fragment {
private void generateWallet() { private void generateWallet() {
if (!checkName()) return; if (!checkName()) return;
if (!checkHeight()) return;
String name = etWalletName.getEditText().getText().toString(); String name = etWalletName.getEditText().getText().toString();
String password = etWalletPassword.getEditText().getText().toString(); String password = etWalletPassword.getEditText().getText().toString();
long height; long height = getHeight();
try { if (height < 0) height = 0;
height = Long.parseLong(etWalletRestoreHeight.getEditText().getText().toString());
} catch (NumberFormatException ex) {
height = 0; // Keep calm and carry on!
}
if (type.equals(TYPE_NEW)) { if (type.equals(TYPE_NEW)) {
bGenerate.setEnabled(false); bGenerate.setEnabled(false);

View File

@@ -34,6 +34,7 @@ import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
@@ -81,7 +82,7 @@ public class GenerateReviewFragment extends Fragment {
bAccept = (Button) view.findViewById(R.id.bAccept); bAccept = (Button) view.findViewById(R.id.bAccept);
boolean testnet = WalletManager.getInstance().isTestNet(); boolean testnet = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
tvWalletMnemonic.setTextIsSelectable(testnet); tvWalletMnemonic.setTextIsSelectable(testnet);
tvWalletSpendKey.setTextIsSelectable(testnet); tvWalletSpendKey.setTextIsSelectable(testnet);
@@ -185,6 +186,7 @@ public class GenerateReviewFragment extends Fragment {
name = wallet.getName(); name = wallet.getName();
status = wallet.getStatus(); status = wallet.getStatus();
if (status != Wallet.Status.Status_Ok) { if (status != Wallet.Status.Status_Ok) {
Timber.e(wallet.getErrorString());
if (closeWallet) wallet.close(); if (closeWallet) wallet.close();
return false; return false;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,7 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter; import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.NetworkType;
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.NodeList; import com.m2049r.xmrwallet.util.NodeList;
@@ -81,11 +82,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
File getStorageRoot(); File getStorageRoot();
boolean onWalletSelected(String wallet, String daemon, boolean testnet); boolean onWalletSelected(String wallet, String daemon);
void onWalletDetails(String wallet, boolean testnet); void onWalletDetails(String wallet);
void onWalletReceive(String wallet, boolean testnet); void onWalletReceive(String wallet);
void onWalletRename(String name); void onWalletRename(String name);
@@ -93,14 +94,16 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void onWalletArchive(String walletName); void onWalletArchive(String walletName);
void onAddWallet(boolean testnet, String type); void onAddWallet(String type);
void showNet(boolean testnet); void showNet();
void setToolbarButton(int type); void setToolbarButton(int type);
void setTitle(String title); void setTitle(String title);
void setNetworkType(NetworkType networkType);
} }
@Override @Override
@@ -126,8 +129,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
super.onResume(); super.onResume();
Timber.d("onResume()"); Timber.d("onResume()");
activityCallback.setTitle(null); activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_DONATE); activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet(isTestnet()); activityCallback.showNet();
} }
@Override @Override
@@ -244,13 +247,13 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
// Callbacks from WalletInfoAdapter // Callbacks from WalletInfoAdapter
@Override @Override
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) { public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
String x = isTestnet() ? "9A-" : "4-"; String addressPrefix = addressPrefix();
if (x.indexOf(infoItem.address.charAt(0)) < 0) { if (addressPrefix.indexOf(infoItem.address.charAt(0)) < 0) {
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show(); Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return; return;
} }
if (activityCallback.onWalletSelected(infoItem.name, getDaemon(), isTestnet())) { if (activityCallback.onWalletSelected(infoItem.name, getDaemon())) {
savePrefs(); savePrefs();
} }
} }
@@ -279,11 +282,24 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
return true; return true;
} }
private String addressPrefix() {
switch (WalletManager.getInstance().getNetworkType()) {
case NetworkType_Testnet:
return "9A-";
case NetworkType_Mainnet:
return "4-";
case NetworkType_Stagenet:
return "5-";
default:
throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
}
}
private void filterList() { private void filterList() {
displayedList.clear(); displayedList.clear();
String x = isTestnet() ? "9A" : "4"; String addressPrefix = addressPrefix();
for (WalletManager.WalletInfo s : walletList) { for (WalletManager.WalletInfo s : walletList) {
if (x.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s); if (addressPrefix.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
} }
} }
@@ -313,11 +329,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
} }
private void showInfo(@NonNull String name) { private void showInfo(@NonNull String name) {
activityCallback.onWalletDetails(name, isTestnet()); activityCallback.onWalletDetails(name);
} }
private void showReceive(@NonNull String name) { private void showReceive(@NonNull String name) {
activityCallback.onWalletReceive(name, isTestnet()); activityCallback.onWalletReceive(name);
} }
@Override @Override
@@ -329,29 +345,31 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.list_menu, menu); inflater.inflate(R.menu.list_menu, menu);
menu.findItem(R.id.action_testnet).setChecked(isTestnet()); menu.findItem(R.id.action_testnet).setChecked(testnetCheckMenu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }
private boolean testnet = BuildConfig.DEBUG; private boolean testnetCheckMenu = BuildConfig.DEBUG;
boolean isTestnet() { //boolean isTestnet() {
return testnet; // return testnet;
} //}
public boolean onTestnetMenuItem() { public boolean onTestnetMenuItem() {
boolean lastState = testnet; boolean lastState = testnetCheckMenu;
setNet(!lastState, true); // set and save setNet(!lastState, true); // set and save
return !lastState; return !lastState;
} }
public void setNet(boolean testnet, boolean save) { public void setNet(boolean testnetChecked, boolean save) {
this.testnet = testnet; this.testnetCheckMenu = testnetChecked;
activityCallback.showNet(testnet); NetworkType net = testnetChecked ? NetworkType.NetworkType_Testnet : NetworkType.NetworkType_Mainnet;
activityCallback.setNetworkType(net);
activityCallback.showNet();
if (save) { if (save) {
savePrefs(true); // use previous state as we just clicked it savePrefs(true); // use previous state as we just clicked it
} }
if (testnet) { if (testnetChecked) {
setDaemon(daemonTestNet); setDaemon(daemonTestNet);
} else { } else {
setDaemon(daemonMainNet); setDaemon(daemonMainNet);
@@ -379,7 +397,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
daemonMainNet = new NodeList(sharedPref.getString(PREF_DAEMON_MAINNET, PREF_DAEMONLIST_MAINNET)); daemonMainNet = new NodeList(sharedPref.getString(PREF_DAEMON_MAINNET, PREF_DAEMONLIST_MAINNET));
daemonTestNet = new NodeList(sharedPref.getString(PREF_DAEMON_TESTNET, PREF_DAEMONLIST_TESTNET)); daemonTestNet = new NodeList(sharedPref.getString(PREF_DAEMON_TESTNET, PREF_DAEMONLIST_TESTNET));
setNet(isTestnet(), false); setNet(testnetCheckMenu, false);
showXmrtoEnabled = sharedPref.getBoolean(PREF_SHOW_XMRTO_ENABLED, true); showXmrtoEnabled = sharedPref.getBoolean(PREF_SHOW_XMRTO_ENABLED, true);
} }
@@ -398,7 +416,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
void savePrefs(boolean usePreviousTestnetState) { void savePrefs(boolean usePreviousTestnetState) {
Timber.d("SAVE / %s", usePreviousTestnetState); Timber.d("SAVE / %s", usePreviousTestnetState);
// save the daemon address for the net // save the daemon address for the net
boolean testnet = isTestnet() ^ usePreviousTestnetState; boolean testnet = testnetCheckMenu ^ usePreviousTestnetState;
String daemon = getDaemon(); String daemon = getDaemon();
if (testnet) { if (testnet) {
daemonTestNet.setRecent(daemon); daemonTestNet.setRecent(daemon);
@@ -484,19 +502,19 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
case R.id.fabNew: case R.id.fabNew:
fabScreen.setVisibility(View.INVISIBLE); fabScreen.setVisibility(View.INVISIBLE);
isFabOpen = false; isFabOpen = false;
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_NEW); activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
break; break;
case R.id.fabView: case R.id.fabView:
animateFAB(); animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_VIEWONLY); activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
break; break;
case R.id.fabKey: case R.id.fabKey:
animateFAB(); animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_KEY); activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
break; break;
case R.id.fabSeed: case R.id.fabSeed:
animateFAB(); animateFAB();
activityCallback.onAddWallet(isTestnet(), GenerateFragment.TYPE_SEED); activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
break; break;
case R.id.fabScreen: case R.id.fabScreen:
animateFAB(); animateFAB();

View File

@@ -300,7 +300,7 @@ public class ReceiveFragment extends Fragment {
String paymentId = etPaymentId.getEditText().getText().toString(); String paymentId = etPaymentId.getEditText().getText().toString();
String xmrAmount = evAmount.getAmount(); String xmrAmount = evAmount.getAmount();
Timber.d("%s/%s/%s", xmrAmount, paymentId, address); Timber.d("%s/%s/%s", xmrAmount, paymentId, address);
if ((xmrAmount == null) || !Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet())) { if ((xmrAmount == null) || !Wallet.isAddressValid(address)) {
clearQR(); clearQR();
Timber.d("CLEARQR"); Timber.d("CLEARQR");
return; return;

View File

@@ -36,7 +36,7 @@ import android.widget.Toast;
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.dialog.DonationFragment; import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment; import com.m2049r.xmrwallet.fragment.send.SendAddressWizardFragment;
import com.m2049r.xmrwallet.fragment.send.SendFragment; import com.m2049r.xmrwallet.fragment.send.SendFragment;
@@ -141,7 +141,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
@Override @Override
protected void onDestroy() { protected void onDestroy() {
Timber.d("onDestroy()"); Timber.d("onDestroy()");
if (!isSynced()) { if ((mBoundService != null) && !isSynced() && (getWallet() != null)) {
saveWallet(); saveWallet();
} }
stopWalletService(); stopWalletService();
@@ -159,8 +159,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
case R.id.action_info: case R.id.action_info:
onWalletDetails(); onWalletDetails();
return true; return true;
case R.id.action_donate: case R.id.action_credits:
DonationFragment.display(getSupportFragmentManager()); CreditsFragment.display(getSupportFragmentManager());
return true; return true;
case R.id.action_share: case R.id.action_share:
onShareTxInfo(); onShareTxInfo();
@@ -213,8 +213,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
case Toolbar.BUTTON_CLOSE: case Toolbar.BUTTON_CLOSE:
finish(); finish();
break; break;
case Toolbar.BUTTON_DONATE: case Toolbar.BUTTON_CREDITS:
Toast.makeText(WalletActivity.this, getString(R.string.label_donate), Toast.LENGTH_SHORT).show(); Toast.makeText(WalletActivity.this, getString(R.string.label_credits), Toast.LENGTH_SHORT).show();
case Toolbar.BUTTON_NONE: case Toolbar.BUTTON_NONE:
default: default:
Timber.e("Button " + type + "pressed - how can this be?"); Timber.e("Button " + type + "pressed - how can this be?");
@@ -222,12 +222,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
} }
}); });
boolean testnet = WalletManager.getInstance().isTestNet(); showNet();
if (testnet) {
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
} else {
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
}
Fragment walletFragment = new WalletFragment(); Fragment walletFragment = new WalletFragment();
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
@@ -238,6 +233,23 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
Timber.d("onCreate() done."); Timber.d("onCreate() done.");
} }
public void showNet() {
switch (WalletManager.getInstance().getNetworkType()) {
case NetworkType_Mainnet:
toolbar.setBackgroundResource(R.drawable.backgound_toolbar_mainnet);
break;
case NetworkType_Testnet:
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
break;
case NetworkType_Stagenet:
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
break;
default:
throw new IllegalStateException("Unsupported Network: " + WalletManager.getInstance().getNetworkType());
}
}
public Wallet getWallet() { public Wallet getWallet() {
if (mBoundService == null) throw new IllegalStateException("WalletService not bound."); if (mBoundService == null) throw new IllegalStateException("WalletService not bound.");
return mBoundService.getWallet(); return mBoundService.getWallet();
@@ -798,7 +810,7 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
@Override @Override
public boolean verifyWalletPassword(String password) { public boolean verifyWalletPassword(String password) {
String walletPath = new File(Helper.getStorageRoot(this), String walletPath = new File(Helper.getWalletRoot(this),
getWalletName() + ".keys").getAbsolutePath(); getWalletName() + ".keys").getAbsolutePath();
return WalletManager.getInstance().verifyWalletPassword(walletPath, password, true); return WalletManager.getInstance().verifyWalletPassword(walletPath, password, true);
} }

View File

@@ -19,13 +19,15 @@ package com.m2049r.xmrwallet;
import android.app.Application; import android.app.Application;
import com.m2049r.xmrwallet.util.Helper;
import timber.log.Timber; import timber.log.Timber;
public class XmrWalletApplication extends Application { public class XmrWalletApplication extends Application {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree()); Timber.plant(new Timber.DebugTree());
} }

View File

@@ -18,6 +18,7 @@ package com.m2049r.xmrwallet.data;
import android.net.Uri; import android.net.Uri;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
@@ -178,7 +179,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!
} }
} }
if (!BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet())) { if (!BitcoinAddressValidator.validate(address)) {
Timber.d("address invalid"); Timber.d("address invalid");
return null; return null;
} }
@@ -190,7 +191,7 @@ public class BarcodeData {
if (address == null) return null; if (address == null) return null;
if (!BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet())) { if (!BitcoinAddressValidator.validate(address)) {
Timber.d("address invalid"); Timber.d("address invalid");
return null; return null;
} }

View File

@@ -0,0 +1,112 @@
/*
* 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.xmrwallet.data;
import com.m2049r.xmrwallet.model.NetworkType;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class WalletNode {
private final String name;
private final String host;
private final int port;
private final String user;
private final String password;
private final NetworkType networkType;
public WalletNode(String walletName, String daemon, NetworkType networkType) {
if ((daemon == null) || daemon.isEmpty())
throw new IllegalArgumentException("daemon is empty");
this.name = walletName;
String daemonAddress;
String a[] = daemon.split("@");
if (a.length == 1) { // no credentials
daemonAddress = a[0];
user = "";
password = "";
} else if (a.length == 2) { // credentials
String userPassword[] = a[0].split(":");
if (userPassword.length != 2)
throw new IllegalArgumentException("User:Password invalid");
user = userPassword[0];
if (!user.isEmpty()) {
password = userPassword[1];
} else {
password = "";
}
daemonAddress = a[1];
} else {
throw new IllegalArgumentException("Too many @");
}
String da[] = daemonAddress.split(":");
if ((da.length > 2) || (da.length < 1))
throw new IllegalArgumentException("Too many ':' or too few");
host = da[0];
if (da.length == 2) {
try {
port = Integer.parseInt(da[1]);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Port not numeric");
}
} else {
switch (networkType) {
case NetworkType_Mainnet:
port = 18081;
break;
case NetworkType_Testnet:
port = 28081;
break;
case NetworkType_Stagenet:
port = 38081;
break;
default:
port = 0;
}
}
this.networkType = networkType;
}
public String getName() {
return name;
}
public NetworkType getNetworkType() {
return networkType;
}
public String getAddress() {
return host + ":" + port;
}
public String getUsername() {
return user;
}
public String getPassword() {
return password;
}
public SocketAddress getSocketAddress() {
return new InetSocketAddress(host, port);
}
public boolean isValid() {
return !host.isEmpty();
}
}

View File

@@ -28,16 +28,14 @@ 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 android.widget.Toast;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.Helper;
public class DonationFragment extends DialogFragment { public class CreditsFragment extends DialogFragment {
static final String TAG = "DonationFragment"; static final String TAG = "DonationFragment";
public static DonationFragment newInstance() { public static CreditsFragment newInstance() {
return new DonationFragment(); return new CreditsFragment();
} }
public static void display(FragmentManager fm) { public static void display(FragmentManager fm) {
@@ -47,24 +45,14 @@ public class DonationFragment extends DialogFragment {
ft.remove(prev); ft.remove(prev);
} }
DonationFragment.newInstance().show(ft, TAG); CreditsFragment.newInstance().show(ft, TAG);
} }
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_donation, null); final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_credits, null);
((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.donation_credits))); ((TextView) view.findViewById(R.id.tvCredits)).setText(Html.fromHtml(getString(R.string.credits_text)));
(view.findViewById(R.id.bCopyAddress)).
setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address),
((TextView) view.findViewById(R.id.tvWalletAddress)).getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
}
});
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(view); builder.setView(view);

View File

@@ -212,7 +212,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
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, WalletManager.getInstance().isTestNet()); || BitcoinAddressValidator.validate(address);
} }
private boolean checkAddress() { private boolean checkAddress() {
@@ -228,13 +228,13 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean isIntegratedAddress() { private boolean isIntegratedAddress() {
String address = etAddress.getEditText().getText().toString(); String address = etAddress.getEditText().getText().toString();
return (address.length() == INTEGRATED_ADDRESS_LENGTH) return (address.length() == INTEGRATED_ADDRESS_LENGTH)
&& Wallet.isAddressValid(address, WalletManager.getInstance().isTestNet()); && Wallet.isAddressValid(address);
} }
private boolean isBitcoinAddress() { private boolean isBitcoinAddress() {
String address = etAddress.getEditText().getText().toString(); String address = etAddress.getEditText().getText().toString();
if ((address.length() >= 27) && (address.length() <= 34)) if ((address.length() >= 27) && (address.length() <= 34))
return BitcoinAddressValidator.validate(address, WalletManager.getInstance().isTestNet()); return BitcoinAddressValidator.validate(address);
else else
return false; return false;
} }

View File

@@ -54,7 +54,8 @@ public class SendSettingsWizardFragment extends SendWizardFragment {
TxData getTxData(); TxData getTxData();
} }
final static int Mixins[] = {4, 7, 12, 25}; // must match the layout XML // Mixin = Ringsize - 1
final static int Mixins[] = {6, 9, 12, 25}; // must match the layout XML / "@array/mixin"
final static PendingTransaction.Priority Priorities[] = final static PendingTransaction.Priority Priorities[] =
{PendingTransaction.Priority.Priority_Default, {PendingTransaction.Priority.Priority_Default,
PendingTransaction.Priority.Priority_Low, PendingTransaction.Priority.Priority_Low,

View File

@@ -77,7 +77,7 @@ public class SendSuccessWizardFragment extends SendWizardFragment {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString()); Helper.clipBoardCopy(getActivity(), getString(R.string.label_send_txid), tvTxId.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), getString(R.string.message_copy_txid), Toast.LENGTH_SHORT).show();
} }
}); });

View File

@@ -137,12 +137,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
ivTxType.setVisibility(View.GONE); // gives us more space for the amount ivTxType.setVisibility(View.GONE); // gives us more space for the amount
} }
long realAmount = infoItem.amount; String displayAmount = Helper.getDisplayAmount(infoItem.amount, Helper.DISPLAY_DIGITS_INFO);
if (infoItem.isPending) {
realAmount = realAmount - infoItem.fee;
}
String displayAmount = Helper.getDisplayAmount(realAmount, Helper.DISPLAY_DIGITS_INFO);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) { if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
tvAmount.setText(context.getString(R.string.tx_list_amount_negative, displayAmount)); tvAmount.setText(context.getString(R.string.tx_list_amount_negative, displayAmount));
} else { } else {

View File

@@ -0,0 +1,45 @@
/*
* 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.xmrwallet.model;
public enum NetworkType {
NetworkType_Mainnet(0),
NetworkType_Testnet(1),
NetworkType_Stagenet(2);
public static NetworkType fromInteger(int n) {
switch (n) {
case 0:
return NetworkType_Mainnet;
case 1:
return NetworkType_Testnet;
case 2:
return NetworkType_Stagenet;
}
return null;
}
public int getValue() {
return value;
}
private int value;
NetworkType(int value) {
this.value = value;
}
}

View File

@@ -84,7 +84,14 @@ public class PendingTransaction {
public native long getFee(); public native long getFee();
public native String getFirstTxId(); public String getFirstTxId() {
String id = getFirstTxIdJ();
if (id == null)
throw new IndexOutOfBoundsException();
return id;
}
public native String getFirstTxIdJ();
public native long getTxCount(); public native long getTxCount();

View File

@@ -79,7 +79,11 @@ public class Wallet {
public native String getPath(); public native String getPath();
public native boolean isTestNet(); public NetworkType getNetworkType() {
return NetworkType.fromInteger(nettype());
}
public native int nettype();
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0; //TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0; //TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
@@ -155,10 +159,10 @@ public class Wallet {
public static native boolean isPaymentIdValid(String payment_id); public static native boolean isPaymentIdValid(String payment_id);
public static boolean isAddressValid(String address) { public static boolean isAddressValid(String address) {
return isAddressValid(address, WalletManager.getInstance().isTestNet()); return isAddressValid(address, WalletManager.getInstance().getNetworkType().getValue());
} }
public static native boolean isAddressValid(String address, boolean isTestNet); public static native boolean isAddressValid(String address, int networkType);
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error); //TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);

View File

@@ -16,6 +16,8 @@
package com.m2049r.xmrwallet.model; package com.m2049r.xmrwallet.model;
import com.m2049r.xmrwallet.data.WalletNode;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
@@ -39,6 +41,7 @@ public class WalletManager {
if (WalletManager.Instance == null) { if (WalletManager.Instance == null) {
WalletManager.Instance = new WalletManager(); WalletManager.Instance = new WalletManager();
} }
return WalletManager.Instance; return WalletManager.Instance;
} }
@@ -69,22 +72,22 @@ public class WalletManager {
} }
public Wallet createWallet(File aFile, String password, String language) { public Wallet createWallet(File aFile, String password, String language) {
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, isTestNet()); long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle); Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet); manageWallet(wallet);
return wallet; return wallet;
} }
private native long createWalletJ(String path, String password, String language, boolean isTestNet); private native long createWalletJ(String path, String password, String language, int networkType);
public Wallet openWallet(String path, String password) { public Wallet openWallet(String path, String password) {
long walletHandle = openWalletJ(path, password, isTestNet()); long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle); Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet); manageWallet(wallet);
return wallet; return wallet;
} }
private native long openWalletJ(String path, String password, boolean isTestNet); private native long openWalletJ(String path, String password, int networkType);
public Wallet recoveryWallet(File aFile, String password, String mnemonic) { public Wallet recoveryWallet(File aFile, String password, String mnemonic) {
return recoveryWallet(aFile, password, mnemonic, 0); return recoveryWallet(aFile, password, mnemonic, 0);
@@ -92,28 +95,28 @@ public class WalletManager {
public Wallet recoveryWallet(File aFile, String password, String mnemonic, long restoreHeight) { public Wallet recoveryWallet(File aFile, String password, String mnemonic, long restoreHeight) {
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), password, mnemonic, long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), password, mnemonic,
isTestNet(), restoreHeight); getNetworkType().getValue(), restoreHeight);
Wallet wallet = new Wallet(walletHandle); Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet); manageWallet(wallet);
return wallet; return wallet;
} }
private native long recoveryWalletJ(String path, String password, String mnemonic, private native long recoveryWalletJ(String path, String password, String mnemonic,
boolean isTestNet, long restoreHeight); int networkType, long restoreHeight);
public Wallet createWalletWithKeys(File aFile, String password, String language, long restoreHeight, public Wallet createWalletWithKeys(File aFile, String password, String language, long restoreHeight,
String addressString, String viewKeyString, String spendKeyString) { String addressString, String viewKeyString, String spendKeyString) {
long walletHandle = createWalletWithKeysJ(aFile.getAbsolutePath(), password, long walletHandle = createWalletFromKeysJ(aFile.getAbsolutePath(), password,
language, isTestNet(), restoreHeight, language, getNetworkType().getValue(), restoreHeight,
addressString, viewKeyString, spendKeyString); addressString, viewKeyString, spendKeyString);
Wallet wallet = new Wallet(walletHandle); Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet); manageWallet(wallet);
return wallet; return wallet;
} }
private native long createWalletWithKeysJ(String path, String password, private native long createWalletFromKeysJ(String path, String password,
String language, String language,
boolean isTestNet, int networkType,
long restoreHeight, long restoreHeight,
String addressString, String addressString,
String viewKeyString, String viewKeyString,
@@ -204,24 +207,23 @@ public class WalletManager {
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0; //TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
private String daemonAddress = null; private String daemonAddress = null;
private boolean testnet = true; private NetworkType networkType = null;
public boolean isTestNet() { public NetworkType getNetworkType() {
if (daemonAddress == null) { return networkType;
return true;
// assume testnet not explicitly initialised
//throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
}
return testnet;
} }
public void setDaemon(String address, boolean testnet, String username, String password) { //public void setDaemon(String address, NetworkType networkType, String username, String password) {
//Timber.d("SETDAEMON " + username + "/" + password + "/" + address); public void setDaemon(WalletNode walletNode) {
this.daemonAddress = address; this.daemonAddress = walletNode.getAddress();
this.testnet = testnet; this.networkType = walletNode.getNetworkType();
this.daemonUsername = username; this.daemonUsername = walletNode.getUsername();
this.daemonPassword = password; this.daemonPassword = walletNode.getPassword();
setDaemonAddressJ(address); setDaemonAddressJ(daemonAddress);
}
public void setNetworkType(NetworkType networkType) {
this.networkType = networkType;
} }
public String getDaemonAddress() { public String getDaemonAddress() {
@@ -268,5 +270,17 @@ public class WalletManager {
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); //TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
static public native void initLogger(String argv0, String defaultLogBaseName);
//TODO: maybe put these in an enum like in monero core - but why?
static public int LOGLEVEL_SILENT = -1;
static public int LOGLEVEL_WARN = 0;
static public int LOGLEVEL_INFO = 1;
static public int LOGLEVEL_DEBUG = 2;
static public int LOGLEVEL_TRACE = 3;
static public int LOGLEVEL_MAX = 4;
static public native void setLogLevel(int level);
static public native void logDebug(String category, String message);
static public native void logInfo(String category, String message);
static public native void logWarning(String category, String message);
static public native void logError(String category, String message);
} }

View File

@@ -524,7 +524,7 @@ public class WalletService extends Service {
showProgress(20); showProgress(20);
Wallet wallet = null; Wallet wallet = null;
WalletManager walletMgr = WalletManager.getInstance(); WalletManager walletMgr = WalletManager.getInstance();
Timber.d("WalletManager testnet=%s", walletMgr.isTestNet()); Timber.d("WalletManager network=%s", walletMgr.getNetworkType().name());
showProgress(30); showProgress(30);
if (walletMgr.walletExists(path)) { if (walletMgr.walletExists(path)) {
Timber.d("open wallet %s", path); Timber.d("open wallet %s", path);

View File

@@ -18,6 +18,9 @@ package com.m2049r.xmrwallet.util;
// based on https://rosettacode.org/wiki/Bitcoin/address_validation#Java // based on https://rosettacode.org/wiki/Bitcoin/address_validation#Java
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager;
import java.math.BigInteger; import java.math.BigInteger;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@@ -27,6 +30,11 @@ public class BitcoinAddressValidator {
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
public static boolean validate(String addrress) {
return validate(addrress,
WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet);
}
public static boolean validate(String addrress, boolean testnet) { public static boolean validate(String addrress, boolean testnet) {
if (addrress.length() < 26 || addrress.length() > 35) if (addrress.length() < 26 || addrress.length() > 35)
return false; return false;
@@ -34,10 +42,11 @@ public class BitcoinAddressValidator {
if (decoded == null) if (decoded == null)
return false; return false;
int v = decoded[0] & 0xFF;
if (!testnet) { if (!testnet) {
if ((decoded[0] != 0x00) && (decoded[0] != 0x05)) return false; if ((v != 0x00) && (v != 0x05)) return false;
} else { } else {
if ((decoded[0] != 0x6f) && (decoded[0] != 0xc4)) return false; if ((v != 0x6f) && (v != 0xc4)) return false;
} }
byte[] hash1 = sha256(Arrays.copyOfRange(decoded, 0, 21)); byte[] hash1 = sha256(Arrays.copyOfRange(decoded, 0, 21));
@@ -57,7 +66,11 @@ public class BitcoinAddressValidator {
byte[] result = new byte[25]; byte[] result = new byte[25];
byte[] numBytes = num.toByteArray(); byte[] numBytes = num.toByteArray();
System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length); if (num.bitLength() == 200) {
System.arraycopy(numBytes, 1, result, 0, 25);
} else {
System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length);
}
return result; return result;
} }

View File

@@ -31,12 +31,16 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable; import android.graphics.drawable.VectorDrawable;
import android.os.Environment; import android.os.Environment;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.system.ErrnoException;
import android.system.Os;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
@@ -55,16 +59,21 @@ import timber.log.Timber;
public class Helper { public class Helper {
static private final String WALLET_DIR = "monerujo"; static private final String WALLET_DIR = "monerujo";
static private final String HOME_DIR = "monero";
static public int DISPLAY_DIGITS_INFO = 5; static public int DISPLAY_DIGITS_INFO = 5;
static public File getStorageRoot(Context context) { static public File getWalletRoot(Context context) {
return getStorage(context, WALLET_DIR);
}
static public File getStorage(Context context, String folderName) {
if (!isExternalStorageWritable()) { if (!isExternalStorageWritable()) {
String msg = context.getString(R.string.message_strorage_not_writable); String msg = context.getString(R.string.message_strorage_not_writable);
Timber.e(msg); Timber.e(msg);
throw new IllegalStateException(msg); throw new IllegalStateException(msg);
} }
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR); File dir = new File(Environment.getExternalStorageDirectory(), folderName);
if (!dir.exists()) { if (!dir.exists()) {
Timber.i("Creating %s", dir.getAbsolutePath()); Timber.i("Creating %s", dir.getAbsolutePath());
dir.mkdirs(); // try to make it dir.mkdirs(); // try to make it
@@ -114,9 +123,9 @@ public class Helper {
} }
static public File getWalletFile(Context context, String aWalletName) { static public File getWalletFile(Context context, String aWalletName) {
File walletDir = getStorageRoot(context); File walletDir = getWalletRoot(context);
File f = new File(walletDir, aWalletName); File f = new File(walletDir, aWalletName);
Timber.d("wallet= %s size= %d", f.getAbsolutePath(), f.length()); Timber.d("wallet=%s size= %d", f.getAbsolutePath(), f.length());
return f; return f;
} }
@@ -263,10 +272,35 @@ public class Helper {
} }
static public HttpUrl getXmrToBaseUrl() { static public HttpUrl getXmrToBaseUrl() {
if ((WalletManager.getInstance() == null) || WalletManager.getInstance().isTestNet()) { if ((WalletManager.getInstance() == null)
|| (WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet)) {
return HttpUrl.parse("https://test.xmr.to/api/v2/xmr2btc/"); return HttpUrl.parse("https://test.xmr.to/api/v2/xmr2btc/");
} else { } else {
return HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/"); return HttpUrl.parse("https://xmr.to/api/v2/xmr2btc/");
} }
} }
static public void setMoneroHome(Context context) {
try {
String home = getStorage(context, HOME_DIR).getAbsolutePath();
Os.setenv("HOME", home, true);
} catch (ErrnoException ex) {
throw new IllegalStateException(ex);
}
}
static public void initLogger(Context context) {
if (BuildConfig.DEBUG) {
initLogger(context, WalletManager.LOGLEVEL_DEBUG);
}
// no logger if not debug
}
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
static public void initLogger(Context context, int level) {
String home = getStorage(context, HOME_DIR).getAbsolutePath();
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
if (level >= WalletManager.LOGLEVEL_SILENT)
WalletManager.setLogLevel(level);
}
} }

View File

@@ -0,0 +1,152 @@
/*
* 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.xmrwallet.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
public class RestoreHeight {
static private RestoreHeight Singleton = null;
static public RestoreHeight getInstance() {
if (Singleton == null) {
synchronized (RestoreHeight.class) {
if (Singleton == null) {
Singleton = new RestoreHeight();
}
}
}
return Singleton;
}
private Map<String, Long> blockheight = new HashMap<>();
RestoreHeight() {
blockheight.put("2014-05-01", 18844L);
blockheight.put("2014-06-01", 65406L);
blockheight.put("2014-07-01", 108882L);
blockheight.put("2014-08-01", 153594L);
blockheight.put("2014-09-01", 198072L);
blockheight.put("2014-10-01", 241088L);
blockheight.put("2014-11-01", 285305L);
blockheight.put("2014-12-01", 328069L);
blockheight.put("2015-01-01", 372369L);
blockheight.put("2015-02-01", 416505L);
blockheight.put("2015-03-01", 456631L);
blockheight.put("2015-04-01", 501084L);
blockheight.put("2015-05-01", 543973L);
blockheight.put("2015-06-01", 588326L);
blockheight.put("2015-07-01", 631187L);
blockheight.put("2015-08-01", 675484L);
blockheight.put("2015-09-01", 719725L);
blockheight.put("2015-10-01", 762463L);
blockheight.put("2015-11-01", 806528L);
blockheight.put("2015-12-01", 849041L);
blockheight.put("2016-01-01", 892866L);
blockheight.put("2016-02-01", 936736L);
blockheight.put("2016-03-01", 977691L);
blockheight.put("2016-04-01", 1015848L);
blockheight.put("2016-05-01", 1037417L);
blockheight.put("2016-06-01", 1059651L);
blockheight.put("2016-07-01", 1081269L);
blockheight.put("2016-08-01", 1103630L);
blockheight.put("2016-09-01", 1125983L);
blockheight.put("2016-10-01", 1147617L);
blockheight.put("2016-11-01", 1169779L);
blockheight.put("2016-12-01", 1191402L);
blockheight.put("2017-01-01", 1213861L);
blockheight.put("2017-02-01", 1236197L);
blockheight.put("2017-03-01", 1256358L);
blockheight.put("2017-04-01", 1278622L);
blockheight.put("2017-05-01", 1300239L);
blockheight.put("2017-06-01", 1322564L);
blockheight.put("2017-07-01", 1344225L);
blockheight.put("2017-08-01", 1366664L);
blockheight.put("2017-09-01", 1389113L);
blockheight.put("2017-10-01", 1410738L);
blockheight.put("2017-11-01", 1433039L);
blockheight.put("2017-12-01", 1454639L);
blockheight.put("2018-01-01", 1477201L);
blockheight.put("2018-02-01", 1499599L);
}
public long getHeight(String date) {
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
parser.setLenient(false);
try {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
cal.set(Calendar.DST_OFFSET, 0);
cal.setTime(parser.parse(date));
cal.add(Calendar.DAY_OF_MONTH, -4); // give it some leeway
if (cal.get(Calendar.YEAR) < 2014)
return 1;
if ((cal.get(Calendar.YEAR) == 2014) && (cal.get(Calendar.MONTH) <= 3))
// before May 2014
return 1;
Calendar query = (Calendar) cal.clone();
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
cal.set(Calendar.DAY_OF_MONTH, 1);
long prevTime = cal.getTimeInMillis();
String prevDate = formatter.format(prevTime);
// lookup blockheight at first of the month
Long prevBc = blockheight.get(prevDate);
if (prevBc == null) {
// if too recent, go back in time and find latest one we have
while (prevBc == null) {
cal.add(Calendar.MONTH, -1);
if (cal.get(Calendar.YEAR) < 2014) {
throw new IllegalStateException("endless loop looking for blockheight");
}
prevTime = cal.getTimeInMillis();
prevDate = formatter.format(prevTime);
prevBc = blockheight.get(prevDate);
}
}
long height = prevBc;
// now we have a blockheight & a date ON or BEFORE the restore date requested
if (date.equals(prevDate)) return height;
// see if we have a blockheight after this date
cal.add(Calendar.MONTH, 1);
long nextTime = cal.getTimeInMillis();
String nextDate = formatter.format(nextTime);
Long nextBc = blockheight.get(nextDate);
if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for
long diff = nextBc - prevBc;
long diffDays = TimeUnit.DAYS.convert(nextTime - prevTime, TimeUnit.MILLISECONDS);
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
TimeUnit.MILLISECONDS);
height = Math.round(prevBc + diff * (1.0 * days / diffDays));
} else {
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
TimeUnit.MILLISECONDS);
height = Math.round(prevBc + 1.0 * days * (24 * 60 / 2));
}
return height;
} catch (ParseException ex) {
throw new IllegalArgumentException(ex);
}
}
}

View File

@@ -45,7 +45,7 @@ public class Toolbar extends android.support.v7.widget.Toolbar {
ImageView toolbarImage; ImageView toolbarImage;
TextView toolbarTitle; TextView toolbarTitle;
TextView toolbarSubtitle; TextView toolbarSubtitle;
Button bDonate; Button bCredits;
public Toolbar(Context context) { public Toolbar(Context context) {
super(context); super(context);
@@ -87,8 +87,8 @@ public class Toolbar extends android.support.v7.widget.Toolbar {
toolbarTitle = (TextView) findViewById(R.id.toolbarTitle); toolbarTitle = (TextView) findViewById(R.id.toolbarTitle);
toolbarSubtitle = (TextView) findViewById(R.id.toolbarSubtitle); toolbarSubtitle = (TextView) findViewById(R.id.toolbarSubtitle);
bDonate = (Button) findViewById(R.id.bDonate); bCredits = (Button) findViewById(R.id.bCredits);
bDonate.setOnClickListener(new View.OnClickListener() { bCredits.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
if (onButtonListener != null) { if (onButtonListener != null) {
onButtonListener.onButton(buttonType); onButtonListener.onButton(buttonType);
@@ -116,43 +116,43 @@ public class Toolbar extends android.support.v7.widget.Toolbar {
public final static int BUTTON_NONE = 0; public final static int BUTTON_NONE = 0;
public final static int BUTTON_BACK = 1; public final static int BUTTON_BACK = 1;
public final static int BUTTON_CLOSE = 2; public final static int BUTTON_CLOSE = 2;
public final static int BUTTON_DONATE = 3; public final static int BUTTON_CREDITS = 3;
public final static int BUTTON_CANCEL = 4; public final static int BUTTON_CANCEL = 4;
int buttonType = BUTTON_DONATE; int buttonType = BUTTON_CREDITS;
public void setButton(int type) { public void setButton(int type) {
switch (type) { switch (type) {
case BUTTON_BACK: case BUTTON_BACK:
Timber.d("BUTTON_BACK"); Timber.d("BUTTON_BACK");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0); bCredits.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0);
bDonate.setText(null); bCredits.setText(null);
bDonate.setVisibility(View.VISIBLE); bCredits.setVisibility(View.VISIBLE);
break; break;
case BUTTON_CLOSE: case BUTTON_CLOSE:
Timber.d("BUTTON_CLOSE"); Timber.d("BUTTON_CLOSE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0); bCredits.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_close); bCredits.setText(R.string.label_close);
bDonate.setVisibility(View.VISIBLE); bCredits.setVisibility(View.VISIBLE);
break; break;
case BUTTON_DONATE: case BUTTON_CREDITS:
Timber.d("BUTTON_DONATE"); Timber.d("BUTTON_CREDITS");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_favorite_white_24dp, 0, 0, 0); bCredits.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_favorite_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_donate); bCredits.setText(R.string.label_credits);
bDonate.setVisibility(View.VISIBLE); bCredits.setVisibility(View.VISIBLE);
break; break;
case BUTTON_CANCEL: case BUTTON_CANCEL:
Timber.d("BUTTON_CANCEL"); Timber.d("BUTTON_CANCEL");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0); bCredits.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_cancel); bCredits.setText(R.string.label_cancel);
bDonate.setVisibility(View.VISIBLE); bCredits.setVisibility(View.VISIBLE);
break; break;
case BUTTON_NONE: case BUTTON_NONE:
default: default:
Timber.d("BUTTON_NONE"); Timber.d("BUTTON_NONE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); bCredits.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
bDonate.setText(null); bCredits.setText(null);
bDonate.setVisibility(View.INVISIBLE); bCredits.setVisibility(View.INVISIBLE);
} }
buttonType = type; buttonType = type;
} }

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:src="@drawable/gunther_coder" />
<TextView
android:id="@+id/tvCredits"
style="@style/MoneroText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"
android:autoLink="web"
android:gravity="center"
android:text="@string/credits_text" />
</LinearLayout>
</ScrollView>

View File

@@ -1,79 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingStart="16dp">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="100dp"
android:src="@drawable/gunther_donate" />
<TextView
style="@style/MoneroText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/donation_text"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"
android:background="@drawable/backgound_spinner"
android:orientation="vertical"
android:padding="@dimen/header_top">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
style="@style/MoneroLabel.Heading.Donation"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:text="@string/donation_address_label" />
<ImageButton
android:id="@+id/bCopyAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginEnd="8dp"
android:background="?android:selectableItemBackground"
android:src="@drawable/ic_content_copy_black_24dp" />
</FrameLayout>
<TextView
android:id="@+id/tvWalletAddress"
style="@style/MoneroText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/data_top"
android:text="@string/donation_address"
android:textAlignment="center"
android:textColor="@color/moneroBlack"
android:textSize="17sp" />
</LinearLayout>
<TextView
android:id="@+id/tvCredits"
style="@style/MoneroText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/header_top"
android:autoLink="web"
android:gravity="center"
android:text="@string/donation_credits" />
</LinearLayout>
</ScrollView>

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