1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-03 08:23:04 +02:00

Compare commits

...

17 Commits

Author SHA1 Message Date
m2049r
f2fe781cb5 update restore heights 2020-09-17 10:18:58 +02:00
m2049r
dcf60ae193 bump version v1.14.3 2020-09-17 09:35:28 +02:00
m2049r
ffdf54c2e1 Merge pull request #677 from m2049r/fix_various
Various fixes & tweaks
2020-09-17 09:34:04 +02:00
m2049r
c060a2ab88 don't show onboarding every time 2020-09-16 19:59:58 +02:00
m2049r
05fc654f3a clean up code 2020-09-16 18:56:27 +02:00
m2049r
c32d157150 monero addresses are 95 characters 2020-09-16 17:51:47 +02:00
m2049r
74e9278baa MainActivity must be singleTop 2020-09-16 17:45:07 +02:00
m2049r
e41e344d63 bump version v1.14.2 2020-09-14 18:16:35 +02:00
m2049r
e66875437d always show onboarding 2020-09-14 18:14:38 +02:00
m2049r
c1d2db3d7d onboarding tweaks & version bump (#676) 2020-09-14 00:24:57 +02:00
m2049r
0c9a2f5e01 fixups 2020-09-13 18:52:26 +02:00
m2049r
64616e3921 onboarding (#675)
Co-authored-by: Stephan <stephan.hagios@gmail.com>
2020-09-13 18:34:22 +02:00
m2049r
82b4d66987 allow spend with fingerprint only (#674) 2020-09-13 18:26:36 +02:00
m2049r
10f2bc6561 upgrade monero core (#673) 2020-09-05 11:01:34 +02:00
m2049r
2ed9a78d9e upgrade gradle 2020-07-16 14:14:26 +02:00
m2049r
ca19f32f8f bump version 2020-07-06 15:00:10 +02:00
m2049r
4431d74051 Fix for Ledger protocol v3 (#670)
* update protocol to V3 for Monero App v1.6.0
2020-07-06 14:59:38 +02:00
35 changed files with 1770 additions and 324 deletions

View File

@@ -7,9 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 28
versionCode 302
versionName "1.13.2 'ReStart'"
versionCode 403
versionName "1.14.3 'On Board'"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -93,6 +92,11 @@ android {
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
@@ -102,6 +106,7 @@ dependencies {
implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion"
implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion"
implementation "com.android.support:swiperefreshlayout:$rootProject.ext.supportVersion"
implementation "com.android.support.constraint:constraint-layout:$rootProject.ext.constraintVersion"
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
@@ -124,5 +129,4 @@ dependencies {
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
testImplementation 'org.json:json:20180813'
testImplementation 'net.jodah:concurrentunit:0.4.4'
}

View File

@@ -20,24 +20,27 @@
android:supportsRtl="true"
android:theme="@style/MyMaterialTheme"
android:usesCleartextTraffic="true">
<activity android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTop"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".WalletActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/wallet_activity_name"
android:launchMode="singleTask"
android:screenOrientation="behind" />
<activity
android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name"
android:launchMode="singleTop"
android:screenOrientation="locked">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
@@ -62,6 +65,10 @@
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/usb_device_filter" />
</activity>
<activity android:name=".onboarding.OnBoardingActivity"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTask"
android:screenOrientation="portrait"/>
<service
android:name=".service.WalletService"
@@ -79,4 +86,4 @@
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>
</manifest>

File diff suppressed because it is too large Load Diff

View File

@@ -315,10 +315,14 @@ public class LoginActivity extends BaseActivity
if (WalletManager.getInstance().walletExists(walletFile)) {
Helper.promptPassword(LoginActivity.this, walletName, true, new Helper.PasswordAction() {
@Override
public void action(String walletName, String password, boolean fingerprintUsed) {
public void act(String walletName, String password, boolean fingerprintUsed) {
if (checkDevice(walletName, password))
startDetails(walletFile, password, GenerateReviewFragment.VIEW_TYPE_DETAILS);
}
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
}
});
} else { // this cannot really happen as we prefilter choices
Timber.e("Wallet missing: %s", walletName);
@@ -348,10 +352,14 @@ public class LoginActivity extends BaseActivity
if (WalletManager.getInstance().walletExists(walletFile)) {
Helper.promptPassword(LoginActivity.this, walletName, false, new Helper.PasswordAction() {
@Override
public void action(String walletName, String password, boolean fingerprintUsed) {
public void act(String walletName, String password, boolean fingerprintUsed) {
if (checkDevice(walletName, password))
startReceive(walletFile, password);
}
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
}
});
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
@@ -1306,10 +1314,15 @@ public class LoginActivity extends BaseActivity
Helper.promptPassword(LoginActivity.this, walletName, false,
new Helper.PasswordAction() {
@Override
public void action(String walletName, String password, boolean fingerprintUsed) {
public void act(String walletName, String password, boolean fingerprintUsed) {
if (checkDevice(walletName, password))
startWallet(walletName, password, fingerprintUsed, streetmode);
}
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
}
});
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2018-2020 EarlOfEgo, 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.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import com.m2049r.xmrwallet.onboarding.OnBoardingActivity;
import com.m2049r.xmrwallet.onboarding.OnBoardingManager;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (OnBoardingManager.shouldShowOnBoarding(getApplicationContext())) {
startActivity(new Intent(this, OnBoardingActivity.class));
} else {
startActivity(new Intent(this, LoginActivity.class));
}
finish();
}
}

View File

@@ -88,7 +88,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
private ActionBarDrawerToggle drawerToggle;
private Toolbar toolbar;
private boolean needVerifyIdentity;
private boolean requestStreetMode = false;
private String password;
@@ -142,7 +141,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
private void enableStreetMode(boolean enable) {
if (enable) {
needVerifyIdentity = true;
streetMode = getWallet().getDaemonBlockChainHeight();
} else {
streetMode = 0;
@@ -200,7 +198,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
if (extras != null) {
acquireWakeLock();
String walletId = extras.getString(REQUEST_ID);
needVerifyIdentity = extras.getBoolean(REQUEST_FINGERPRINT_USED);
// we can set the streetmode height AFTER opening the wallet
requestStreetMode = extras.getBoolean(REQUEST_STREETMODE);
password = extras.getString(REQUEST_PW);
@@ -333,7 +330,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
private void onDisableStreetMode() {
Helper.promptPassword(WalletActivity.this, getWallet().getName(), false, new Helper.PasswordAction() {
@Override
public void action(String walletName, String password, boolean fingerprintUsed) {
public void act(String walletName, String password, boolean fingerprintUsed) {
runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -342,6 +339,10 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
}
});
}
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
}
});
}
@@ -855,17 +856,16 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
final Bundle extras = new Bundle();
extras.putString(GenerateReviewFragment.REQUEST_TYPE, GenerateReviewFragment.VIEW_TYPE_WALLET);
if (needVerifyIdentity) {
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
@Override
public void action(String walletName, String password, boolean fingerprintUsed) {
replaceFragment(new GenerateReviewFragment(), null, extras);
needVerifyIdentity = false;
}
});
} else {
replaceFragment(new GenerateReviewFragment(), null, extras);
}
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
@Override
public void act(String walletName, String password, boolean fingerprintUsed) {
replaceFragment(new GenerateReviewFragment(), null, extras);
}
@Override
public void fail(String walletName, String password, boolean fingerprintUsed) {
}
});
break;
case DialogInterface.BUTTON_NEGATIVE:
@@ -1003,12 +1003,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
return getWallet().getUnlockedBalance();
}
@Override
public boolean verifyWalletPassword(String password) {
String walletPassword = Helper.getWalletPassword(getApplicationContext(), getWalletName(), password);
return walletPassword != null;
}
@Override
public void onBackPressed() {
if (drawer.isDrawerOpen(GravityCompat.START)) {

View File

@@ -346,103 +346,16 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
public void preSend() {
final Activity activity = getActivity();
View promptsView = getLayoutInflater().inflate(R.layout.prompt_password, null);
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
alertDialogBuilder.setView(promptsView);
final TextInputLayout etPassword = promptsView.findViewById(R.id.etPassword);
etPassword.setHint(getString(R.string.prompt_send_password));
etPassword.getEditText().addTextChangedListener(new TextWatcher() {
Helper.promptPassword(getContext(), getActivityCallback().getWalletName(), false, new Helper.PasswordAction() {
@Override
public void afterTextChanged(Editable s) {
if (etPassword.getError() != null) {
etPassword.setError(null);
}
public void act(String walletName, String password, boolean fingerprintUsed) {
send();
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
public void fail(String walletName, String password, boolean fingerprintUsed) {
bSend.setEnabled(sendCountdown > 0); // allow to try again
}
});
alertDialogBuilder
.setCancelable(false)
.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
String pass = etPassword.getEditText().getText().toString();
if (getActivityCallback().verifyWalletPassword(pass)) {
dialog.dismiss();
Helper.hideKeyboardAlways(activity);
send();
} else {
etPassword.setError(getString(R.string.bad_password));
}
}
})
.setNegativeButton(getString(R.string.label_cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways(activity);
dialog.cancel();
bSend.setEnabled(sendCountdown > 0); // allow to try again
}
});
final android.app.AlertDialog passwordDialog = alertDialogBuilder.create();
passwordDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button button = ((android.app.AlertDialog) dialog).getButton(android.app.AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String pass = etPassword.getEditText().getText().toString();
if (getActivityCallback().verifyWalletPassword(pass)) {
Helper.hideKeyboardAlways(activity);
passwordDialog.dismiss();
send();
} else {
etPassword.setError(getString(R.string.bad_password));
}
}
});
}
});
Helper.showKeyboard(passwordDialog);
// accept keyboard "ok"
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
String pass = etPassword.getEditText().getText().toString();
if (getActivityCallback().verifyWalletPassword(pass)) {
Helper.hideKeyboardAlways(activity);
passwordDialog.dismiss();
send();
} else {
etPassword.setError(getString(R.string.bad_password));
}
return true;
}
return false;
}
});
if (Helper.preventScreenshot()) {
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
passwordDialog.show();
}
// creates a pending transaction and calls us back with transactionCreated()

View File

@@ -141,7 +141,12 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
void send() {
sendListener.commitTransaction();
pbProgressSend.setVisibility(View.VISIBLE);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
pbProgressSend.setVisibility(View.VISIBLE);
}
});
}
@Override
@@ -225,103 +230,16 @@ public class SendConfirmWizardFragment extends SendWizardFragment implements Sen
}
public void preSend() {
final Activity activity = getActivity();
View promptsView = getLayoutInflater().inflate(R.layout.prompt_password, null);
android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(activity);
alertDialogBuilder.setView(promptsView);
final TextInputLayout etPassword = promptsView.findViewById(R.id.etPassword);
etPassword.setHint(getString(R.string.prompt_send_password));
etPassword.getEditText().addTextChangedListener(new TextWatcher() {
Helper.promptPassword(getContext(), getActivityCallback().getWalletName(), false, new Helper.PasswordAction() {
@Override
public void afterTextChanged(Editable s) {
if (etPassword.getError() != null) {
etPassword.setError(null);
}
public void act(String walletName, String password, boolean fingerprintUsed) {
send();
}
@Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
public void fail(String walletName, String password, boolean fingerprintUsed) {
bSend.setEnabled(true); // allow to try again
}
});
alertDialogBuilder
.setCancelable(false)
.setPositiveButton(getString(R.string.label_ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
String pass = etPassword.getEditText().getText().toString();
if (getActivityCallback().verifyWalletPassword(pass)) {
dialog.dismiss();
Helper.hideKeyboardAlways(activity);
send();
} else {
etPassword.setError(getString(R.string.bad_password));
}
}
})
.setNegativeButton(getString(R.string.label_cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways(activity);
dialog.cancel();
bSend.setEnabled(true); // allow to try again
}
});
final android.app.AlertDialog passwordDialog = alertDialogBuilder.create();
passwordDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button button = ((android.app.AlertDialog) dialog).getButton(android.app.AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String pass = etPassword.getEditText().getText().toString();
if (getActivityCallback().verifyWalletPassword(pass)) {
Helper.hideKeyboardAlways(activity);
passwordDialog.dismiss();
send();
} else {
etPassword.setError(getString(R.string.bad_password));
}
}
});
}
});
Helper.showKeyboard(passwordDialog);
// accept keyboard "ok"
etPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
String pass = etPassword.getEditText().getText().toString();
if (getActivityCallback().verifyWalletPassword(pass)) {
Helper.hideKeyboardAlways(activity);
passwordDialog.dismiss();
send();
} else {
etPassword.setError(getString(R.string.bad_password));
}
return true;
}
return false;
}
});
if (Helper.preventScreenshot()) {
passwordDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
passwordDialog.show();
}
// creates a pending transaction and calls us back with transactionCreated()

View File

@@ -75,7 +75,7 @@ public class SendFragment extends Fragment
void onPrepareSend(String tag, TxData data);
boolean verifyWalletPassword(String password);
String getWalletName();
void onSend(UserNotes notes);

View File

@@ -17,12 +17,15 @@
package com.m2049r.xmrwallet.ledger;
public enum Instruction {
INS_NONE(0x00),
INS_RESET(0x02),
INS_GET_KEY(0x20),
INS_DISPLAY_ADDRESS(0x21),
INS_PUT_KEY(0x22),
INS_GET_CHACHA8_PREKEY(0x24),
INS_VERIFY_KEY(0x26),
INS_MANAGE_SEEDWORDS(0x28),
INS_SECRET_KEY_TO_PUBLIC_KEY(0x30),
INS_GEN_KEY_DERIVATION(0x32),
@@ -30,6 +33,7 @@ public enum Instruction {
INS_DERIVE_PUBLIC_KEY(0x36),
INS_DERIVE_SECRET_KEY(0x38),
INS_GEN_KEY_IMAGE(0x3A),
INS_SECRET_KEY_ADD(0x3C),
INS_SECRET_KEY_SUB(0x3E),
INS_GENERATE_KEYPAIR(0x40),
@@ -45,15 +49,20 @@ public enum Instruction {
INS_SET_SIGNATURE_MODE(0x72),
INS_GET_ADDITIONAL_KEY(0x74),
INS_STEALTH(0x76),
INS_GEN_COMMITMENT_MASK(0x77),
INS_BLIND(0x78),
INS_UNBLIND(0x7A),
INS_GEN_TXOUT_KEYS(0x7B),
INS_VALIDATE(0x7C),
INS_PREFIX_HASH(0x7D),
INS_MLSAG(0x7E),
INS_CLOSE_TX(0x80),
INS_GET_RESPONSE(0xc0),
INS_GET_TX_PROOF(0xA0),
INS_UNDEFINED(0xff);
INS_GET_RESPONSE(0xC0),
INS_UNDEFINED(0xFF);
public static Instruction fromByte(byte n) {
switch (n & 0xFF) {

View File

@@ -42,11 +42,11 @@ public class Ledger {
static public final int LOOKAHEAD_SUBADDRESSES = 20;
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
private static final byte PROTOCOL_VERSION = 0x02;
private static final byte PROTOCOL_VERSION = 0x03;
public static final int SW_OK = 0x9000;
public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
public static final int OK[] = {SW_OK};
public static final int MINIMUM_LEDGER_VERSION = (1 << 16) + (3 << 8) + (1); // 1.3.1
public static final int MINIMUM_LEDGER_VERSION = (1 << 16) + (6 << 8) + (0); // 1.6.0
public static UsbDevice findDevice(UsbManager usbManager) {
if (!ENABLED) return null;

View File

@@ -51,6 +51,7 @@ public class LedgerProgressDialog extends ProgressDialog implements Ledger.Liste
switch (ins) {
case INS_RESET: // ledger may ask for confirmation - maybe a bug?
case INS_GET_KEY: // ledger asks for confirmation to send keys
case INS_DISPLAY_ADDRESS:
setIndeterminate(true);
setMessage(getContext().getString(R.string.progress_ledger_confirm));
break;
@@ -102,6 +103,11 @@ public class LedgerProgressDialog extends ProgressDialog implements Ledger.Liste
setMessage(getContext().getString(R.string.progress_ledger_mlsag));
}
break;
case INS_PREFIX_HASH:
if ((apdu[2] != 1) || (apdu[3] != 0)) break;
setIndeterminate(true);
setMessage(getContext().getString(R.string.progress_ledger_confirm));
break;
case INS_VALIDATE:
if ((apdu[2] != 1) || (apdu[3] != 1)) break;
validate = true;

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) 2018-2020 EarlOfEgo, 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.onboarding;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.util.TypedValue;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import com.m2049r.xmrwallet.LoginActivity;
import com.m2049r.xmrwallet.R;
public class OnBoardingActivity extends AppCompatActivity implements OnBoardingAdapter.Listener {
private OnBoardingViewPager pager;
private OnBoardingAdapter pagerAdapter;
private Button nextButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_on_boarding);
nextButton = findViewById(R.id.buttonNext);
pager = findViewById(R.id.pager);
pagerAdapter = new OnBoardingAdapter(getApplicationContext(), this);
pager.setAdapter(pagerAdapter);
int pixels = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics());
pager.setPageMargin(pixels);
pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
setButtonState(position);
}
});
final TabLayout tabLayout = (TabLayout) findViewById(R.id.tabLayout);
if (pagerAdapter.getCount() > 1) {
tabLayout.setupWithViewPager(pager, true);
LinearLayout tabStrip = ((LinearLayout) tabLayout.getChildAt(0));
for (int i = 0; i < tabStrip.getChildCount(); i++) {
tabStrip.getChildAt(i).setClickable(false);
}
} else {
tabLayout.setVisibility(View.GONE);
}
nextButton.setOnClickListener(v -> {
final int item = pager.getCurrentItem();
if (item + 1 >= pagerAdapter.getCount()) {
finishOnboarding();
} else {
pager.setCurrentItem(item + 1);
}
});
for (int i = 0; i < OnBoardingScreen.values().length; i++) {
agreed[i] = !OnBoardingScreen.values()[i].isMustAgree();
}
setButtonState(0);
}
private void finishOnboarding() {
OnBoardingManager.setOnBoardingShown(getApplicationContext());
startActivity(new Intent(this, LoginActivity.class));
finish();
}
boolean[] agreed = new boolean[OnBoardingScreen.values().length];
@Override
public void setAgreeClicked(int position, boolean isChecked) {
agreed[position] = isChecked;
setButtonState(position);
}
@Override
public boolean isAgreeClicked(int position) {
return agreed[position];
}
@Override
public void setButtonState(int position) {
nextButton.setEnabled(agreed[position]);
if (nextButton.isEnabled())
pager.setAllowedSwipeDirection(OnBoardingViewPager.SwipeDirection.ALL);
else
pager.setAllowedSwipeDirection(OnBoardingViewPager.SwipeDirection.LEFT);
if (pager.getCurrentItem() + 1 == pagerAdapter.getCount()) { // last page
nextButton.setText(R.string.onboarding_button_ready);
} else {
nextButton.setText(R.string.onboarding_button_next);
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2018-2020 EarlOfEgo, 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.onboarding;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.PagerAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import timber.log.Timber;
public class OnBoardingAdapter extends PagerAdapter {
interface Listener {
void setAgreeClicked(int position, boolean isChecked);
boolean isAgreeClicked(int position);
void setButtonState(int position);
}
private final Context context;
private Listener listener;
OnBoardingAdapter(final Context context, final Listener listener) {
this.context = context;
this.listener = listener;
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup collection, int position) {
LayoutInflater inflater = LayoutInflater.from(context);
final View view = inflater.inflate(R.layout.view_onboarding, collection, false);
final OnBoardingScreen onBoardingScreen = OnBoardingScreen.values()[position];
final Drawable drawable = ContextCompat.getDrawable(context, onBoardingScreen.getDrawable());
((ImageView) view.findViewById(R.id.onboardingImage)).setImageDrawable(drawable);
((TextView) view.findViewById(R.id.onboardingTitle)).setText(onBoardingScreen.getTitle());
((TextView) view.findViewById(R.id.onboardingInformation)).setText(onBoardingScreen.getInformation());
if (onBoardingScreen.isMustAgree()) {
final CheckBox agree = ((CheckBox) view.findViewById(R.id.onboardingAgree));
agree.setVisibility(View.VISIBLE);
agree.setChecked(listener.isAgreeClicked(position));
agree.setOnClickListener(v -> {
listener.setAgreeClicked(position, ((CheckBox) v).isChecked());
});
}
collection.addView(view);
return view;
}
@Override
public int getCount() {
return OnBoardingScreen.values().length;
}
@Override
public void destroyItem(@NonNull ViewGroup collection, int position, @NonNull Object view) {
Timber.d("destroy " + position);
collection.removeView((View) view);
}
@Override
public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) {
return view == object;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2018-2020 EarlOfEgo, 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.onboarding;
import android.content.Context;
import android.content.SharedPreferences;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
import java.util.Date;
import timber.log.Timber;
public class OnBoardingManager {
private static final String PREFS_ONBOARDING = "PREFS_ONBOARDING";
private static final String ONBOARDING_SHOWN = "ONBOARDING_SHOWN";
public static boolean shouldShowOnBoarding(final Context context) {
return !getSharedPreferences(context).contains(ONBOARDING_SHOWN);
}
public static void setOnBoardingShown(final Context context) {
Timber.d("Set onboarding shown.");
SharedPreferences sharedPreferences = getSharedPreferences(context);
sharedPreferences.edit().putLong(ONBOARDING_SHOWN, new Date().getTime()).apply();
}
public static void clearOnBoardingShown(final Context context) {
SharedPreferences sharedPreferences = getSharedPreferences(context);
sharedPreferences.edit().remove(ONBOARDING_SHOWN).apply();
}
private static SharedPreferences getSharedPreferences(final Context context) {
return context.getSharedPreferences(PREFS_ONBOARDING, Context.MODE_PRIVATE);
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) 2018-2020 EarlOfEgo, 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.onboarding;
import com.m2049r.xmrwallet.R;
enum OnBoardingScreen {
WELCOME(R.string.onboarding_welcome_title, R.string.onboarding_welcome_information, R.drawable.ic_onboarding_welcome, false),
SEED(R.string.onboarding_seed_title, R.string.onboarding_seed_information, R.drawable.ic_onboarding_seed, true),
FPSEND(R.string.onboarding_fpsend_title, R.string.onboarding_fpsend_information, R.drawable.ic_onboarding_fingerprint, false),
XMRTO(R.string.onboarding_xmrto_title, R.string.onboarding_xmrto_information, R.drawable.ic_onboarding_xmrto, false),
NODES(R.string.onboarding_nodes_title, R.string.onboarding_nodes_information, R.drawable.ic_onboarding_nodes, false);
private final int title;
private final int information;
private final int drawable;
private final boolean mustAgree;
OnBoardingScreen(final int title, final int information, final int drawable, final boolean mustAgree) {
this.title = title;
this.information = information;
this.drawable = drawable;
this.mustAgree = mustAgree;
}
public int getTitle() {
return title;
}
public int getInformation() {
return information;
}
public int getDrawable() {
return drawable;
}
public boolean isMustAgree() {
return mustAgree;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2020 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// based on https://stackoverflow.com/a/34076649
package com.m2049r.xmrwallet.onboarding;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class OnBoardingViewPager extends ViewPager {
public enum SwipeDirection {
ALL, LEFT, RIGHT, NONE;
}
private float initialXValue;
private SwipeDirection direction;
public OnBoardingViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.direction = SwipeDirection.ALL;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (this.IsSwipeAllowed(event)) {
return super.onTouchEvent(event);
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (this.IsSwipeAllowed(event)) {
return super.onInterceptTouchEvent(event);
}
return false;
}
private boolean IsSwipeAllowed(MotionEvent event) {
if (this.direction == SwipeDirection.ALL) return true;
if (direction == SwipeDirection.NONE)//disable any swipe
return false;
if (event.getAction() == MotionEvent.ACTION_DOWN) {
initialXValue = event.getX();
return true;
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
float diffX = event.getX() - initialXValue;
if (diffX > 0 && direction == SwipeDirection.RIGHT) {
// swipe from left to right detected
return false;
} else if (diffX < 0 && direction == SwipeDirection.LEFT) {
// swipe from right to left detected
return false;
}
}
return true;
}
public void setAllowedSwipeDirection(SwipeDirection direction) {
this.direction = direction;
}
}

View File

@@ -417,7 +417,7 @@ public class Helper {
}
static AlertDialog openDialog = null; // for preventing opening of multiple dialogs
static AsyncTask<Void, Void, Boolean> loginTask = null;
static AsyncTask<Void, Void, Boolean> passwordTask = null;
static public void promptPassword(final Context context, final String wallet, boolean fingerprintDisabled, final PasswordAction action) {
if (openDialog != null) return; // we are already asking for password
@@ -442,11 +442,11 @@ public class Helper {
final AtomicBoolean incorrectSavedPass = new AtomicBoolean(false);
class LoginWalletTask extends AsyncTask<Void, Void, Boolean> {
class PasswordTask extends AsyncTask<Void, Void, Boolean> {
private String pass;
private boolean fingerprintUsed;
LoginWalletTask(String pass, boolean fingerprintUsed) {
PasswordTask(String pass, boolean fingerprintUsed) {
this.pass = pass;
this.fingerprintUsed = fingerprintUsed;
}
@@ -488,7 +488,7 @@ public class Helper {
etPassword.setError(context.getString(R.string.bad_password));
}
}
loginTask = null;
passwordTask = null;
}
}
@@ -521,9 +521,9 @@ public class Helper {
public void onClick(DialogInterface dialog, int id) {
Helper.hideKeyboardAlways((Activity) context);
cancelSignal.cancel();
if (loginTask != null) {
loginTask.cancel(true);
loginTask = null;
if (passwordTask != null) {
passwordTask.cancel(true);
passwordTask = null;
}
dialog.cancel();
openDialog = null;
@@ -552,9 +552,9 @@ public class Helper {
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
try {
String userPass = KeyStoreHelper.loadWalletUserPass(context, wallet);
if (loginTask == null) {
loginTask = new LoginWalletTask(userPass, true);
loginTask.execute();
if (passwordTask == null) {
passwordTask = new PasswordTask(userPass, true);
passwordTask.execute();
}
} catch (KeyStoreHelper.BrokenPasswordStoreException ex) {
etPassword.setError(context.getString(R.string.bad_password));
@@ -586,9 +586,9 @@ public class Helper {
@Override
public void onClick(View view) {
String pass = etPassword.getEditText().getText().toString();
if (loginTask == null) {
loginTask = new LoginWalletTask(pass, false);
loginTask.execute();
if (passwordTask == null) {
passwordTask = new PasswordTask(pass, false);
passwordTask.execute();
}
}
});
@@ -601,9 +601,9 @@ public class Helper {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
String pass = etPassword.getEditText().getText().toString();
if (loginTask == null) {
loginTask = new LoginWalletTask(pass, false);
loginTask.execute();
if (passwordTask == null) {
passwordTask = new PasswordTask(pass, false);
passwordTask.execute();
}
return true;
}
@@ -620,15 +620,18 @@ public class Helper {
}
public interface PasswordAction {
void action(String walletName, String password, boolean fingerprintUsed);
void act(String walletName, String password, boolean fingerprintUsed);
void fail(String walletName, String password, boolean fingerprintUsed);
}
static private boolean processPasswordEntry(Context context, String walletName, String pass, boolean fingerprintUsed, PasswordAction action) {
String walletPassword = Helper.getWalletPassword(context, walletName, pass);
if (walletPassword != null) {
action.action(walletName, walletPassword, fingerprintUsed);
action.act(walletName, walletPassword, fingerprintUsed);
return true;
} else {
action.fail(walletName, walletPassword, fingerprintUsed);
return false;
}
}

View File

@@ -140,6 +140,11 @@ public class KeyStoreHelper {
}
}
public static boolean hasStoredPasswords(@NonNull Context context) {
SharedPreferences prefs = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getAll().size() > 0;
}
public static String loadWalletUserPass(@NonNull Context context, String wallet) throws BrokenPasswordStoreException {
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
String encoded = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)

View File

@@ -116,6 +116,9 @@ public class RestoreHeight {
blockheight.put("2020-04-01", 2066806L);
blockheight.put("2020-05-01", 2088411L);
blockheight.put("2020-06-01", 2110702L);
blockheight.put("2020-07-01", 2132318L);
blockheight.put("2020-08-01", 2154590L);
blockheight.put("2020-09-01", 2176790L);
}
public long getHeight(String date) {

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thickness="8dp"
android:useLevel="false">
<solid android:color="@color/gradientOrange" />
</shape>
</item>
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="ring"
android:thickness="8dp"
android:useLevel="false">
<solid android:color="#CDD1D9" />
</shape>
</item>
</layer-list>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/dot_dark" android:state_selected="true" />
<item android:drawable="@drawable/dot_light" />
</selector>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.m2049r.xmrwallet.onboarding.OnBoardingActivity"
tools:layout_editor_absoluteY="25dp">
<Button
android:id="@+id/buttonNext"
style="@style/MoneroButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:elevation="4dp"
android:text="@string/onboarding_button_next"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.m2049r.xmrwallet.onboarding.OnBoardingViewPager
android:id="@+id/pager"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="@color/white"
android:clipToPadding="false"
android:elevation="2dp"
app:layout_constraintBottom_toTopOf="@+id/tabLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<android.support.design.widget.TabLayout
android:id="@+id/tabLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:elevation="2dp"
app:layout_constraintBottom_toTopOf="@+id/buttonNext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tabBackground="@drawable/onboarding_dots"
app:tabGravity="center"
app:tabIndicatorHeight="0dp" />
</android.support.constraint.ConstraintLayout>

View File

@@ -36,7 +36,7 @@
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/bPasteAddress"
app:counterEnabled="true"
app:counterMaxLength="16"
app:counterMaxLength="95"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<ImageView
android:id="@+id/onboardingImage"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="32dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="32dp"
app:layout_constraintBottom_toTopOf="@+id/onboardingTitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/gunther_24dp" />
<TextView
android:id="@+id/onboardingInformation"
style="@style/MoneroText.Info"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="8dp"
android:gravity="center"
android:lineSpacingMultiplier="1.2"
app:layout_constraintBottom_toTopOf="@+id/onboardingAgree"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/onboardingTitle"
tools:text="@string/onboarding_fpsend_information" />
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.6" />
<TextView
android:id="@+id/onboardingTitle"
style="@style/MoneroText.Balance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/guideline"
tools:text="@string/onboarding_fpsend_title" />
<CheckBox
android:id="@+id/onboardingAgree"
style="@style/MoneroLabel.Caps.Black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="@string/onboarding_agree"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/onboardingInformation" />
</android.support.constraint.ConstraintLayout>

View File

@@ -15,7 +15,7 @@
<color name="moneroWhite">#ffffff</color>
<color name="moneroBlack">#000000</color>
<color name="moneroBlue">#1F4E97</color>
<color name="moneroBlue">#1f4e97</color>
<color name="moneroGray">#FD9B9B9B</color>
<color name="moneroStreetA">#D81F0759</color>

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