mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-07 11:35:59 +02:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2c2a5314d4 | ||
![]() |
669516c60b | ||
![]() |
a56a29a6c4 | ||
![]() |
4e31f47482 | ||
![]() |
c1f14f9653 | ||
![]() |
2746c52d7b | ||
![]() |
5df323bacb | ||
![]() |
776cc26377 | ||
![]() |
bdfb6a90b6 | ||
![]() |
6db44dfab1 | ||
![]() |
c68ac7db6d | ||
![]() |
e09862e940 | ||
![]() |
c7bd7469a1 | ||
![]() |
b39857fd2e | ||
![]() |
8170f823ab | ||
![]() |
38c0ead45c | ||
![]() |
1d027c1694 | ||
![]() |
45dc21fbf7 | ||
![]() |
d4f4de234a | ||
![]() |
394d5471e3 | ||
![]() |
beb098adb3 | ||
![]() |
fda370d35b | ||
![]() |
99681e1bbb | ||
![]() |
21f44380b1 | ||
![]() |
f4c1af1bb8 | ||
![]() |
c002b81ebd | ||
![]() |
d2f07ba3b6 | ||
![]() |
24fc27b09e | ||
![]() |
cf4ff856d5 |
@@ -1,14 +1,15 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
buildToolsVersion '29.0.3'
|
buildToolsVersion '30.0.3'
|
||||||
|
ndkVersion '17.2.4988734'
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.m2049r.xmrwallet"
|
applicationId "com.m2049r.xmrwallet"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 713
|
versionCode 1007
|
||||||
versionName "1.17.13 'Druk'"
|
versionName "2.0.7 'Puginarug'"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
@@ -123,14 +124,14 @@ dependencies {
|
|||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'com.google.android.material:material:1.3.0'
|
implementation 'com.google.android.material:material:1.3.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.2.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
|
|
||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||||
implementation "com.squareup.okhttp3:okhttp:4.9.0"
|
implementation "com.squareup.okhttp3:okhttp:4.9.0"
|
||||||
implementation "com.burgstaller:okhttp-digest:2.1"
|
implementation "io.github.rburgst:okhttp-digest:2.5"
|
||||||
implementation "com.jakewharton.timber:timber:4.7.1"
|
implementation "com.jakewharton.timber:timber:4.7.1"
|
||||||
|
|
||||||
implementation 'com.nulab-inc:zxcvbn:1.3.0'
|
implementation 'com.nulab-inc:zxcvbn:1.3.0'
|
||||||
|
@@ -4,20 +4,19 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:requestLegacyExternalStorage="true"
|
|
||||||
android:name=".XmrWalletApplication"
|
android:name=".XmrWalletApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:preserveLegacyExternalStorage="true"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/MyMaterialTheme"
|
android:theme="@style/MyMaterialTheme"
|
||||||
android:usesCleartextTraffic="true">
|
android:usesCleartextTraffic="true">
|
||||||
@@ -36,7 +35,7 @@
|
|||||||
android:configChanges="orientation|keyboardHidden|uiMode"
|
android:configChanges="orientation|keyboardHidden|uiMode"
|
||||||
android:label="@string/wallet_activity_name"
|
android:label="@string/wallet_activity_name"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:screenOrientation="behind"/>
|
android:screenOrientation="behind" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".LoginActivity"
|
android:name=".LoginActivity"
|
||||||
android:configChanges="orientation|keyboardHidden|uiMode"
|
android:configChanges="orientation|keyboardHidden|uiMode"
|
||||||
|
@@ -31,10 +31,11 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.data.BarcodeData;
|
import com.m2049r.xmrwallet.data.BarcodeData;
|
||||||
import com.m2049r.xmrwallet.dialog.ProgressDialog;
|
import com.m2049r.xmrwallet.dialog.ProgressDialog;
|
||||||
@@ -46,7 +47,8 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class BaseActivity extends SecureActivity implements GenerateReviewFragment.ProgressListener {
|
public class BaseActivity extends SecureActivity
|
||||||
|
implements GenerateReviewFragment.ProgressListener, SubaddressFragment.ProgressListener {
|
||||||
|
|
||||||
ProgressDialog progressDialog = null;
|
ProgressDialog progressDialog = null;
|
||||||
|
|
||||||
|
@@ -307,7 +307,6 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
void dismissProgressDialog();
|
void dismissProgressDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public interface AcceptListener {
|
public interface AcceptListener {
|
||||||
void onAccept(String name, String password);
|
void onAccept(String name, String password);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -31,7 +31,6 @@ import android.widget.FrameLayout;
|
|||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -62,8 +61,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
|
|
||||||
private WalletInfoAdapter adapter;
|
private WalletInfoAdapter adapter;
|
||||||
|
|
||||||
private List<WalletManager.WalletInfo> walletList = new ArrayList<>();
|
private final List<WalletManager.WalletInfo> walletList = new ArrayList<>();
|
||||||
private List<WalletManager.WalletInfo> displayedList = new ArrayList<>();
|
|
||||||
|
|
||||||
private View tvGuntherSays;
|
private View tvGuntherSays;
|
||||||
private ImageView ivGunther;
|
private ImageView ivGunther;
|
||||||
@@ -82,13 +80,15 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
|
|
||||||
void onWalletDetails(String wallet);
|
void onWalletDetails(String wallet);
|
||||||
|
|
||||||
void onWalletReceive(String wallet);
|
|
||||||
|
|
||||||
void onWalletRename(String name);
|
void onWalletRename(String name);
|
||||||
|
|
||||||
void onWalletBackup(String name);
|
void onWalletBackup(String name);
|
||||||
|
|
||||||
void onWalletArchive(String walletName);
|
void onWalletRestore();
|
||||||
|
|
||||||
|
void onWalletDelete(String walletName);
|
||||||
|
|
||||||
|
void onWalletDeleteCache(String walletName);
|
||||||
|
|
||||||
void onAddWallet(String type);
|
void onAddWallet(String type);
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(@NonNull Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
if (context instanceof Listener) {
|
if (context instanceof Listener) {
|
||||||
this.activityCallback = (Listener) context;
|
this.activityCallback = (Listener) context;
|
||||||
@@ -202,12 +202,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
// Wallet touched
|
// Wallet touched
|
||||||
@Override
|
@Override
|
||||||
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
|
public void onInteraction(final View view, final WalletManager.WalletInfo infoItem) {
|
||||||
String addressPrefix = WalletManager.getInstance().addressPrefix();
|
openWallet(infoItem.getName(), false);
|
||||||
if (addressPrefix.indexOf(infoItem.address.charAt(0)) < 0) {
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
openWallet(infoItem.name, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openWallet(String name, boolean streetmode) {
|
private void openWallet(String name, boolean streetmode) {
|
||||||
@@ -216,51 +211,34 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo listItem) {
|
public boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo listItem) {
|
||||||
switch (item.getItemId()) {
|
final int id = item.getItemId();
|
||||||
case R.id.action_streetmode:
|
if (id == R.id.action_streetmode) {
|
||||||
openWallet(listItem.name, true);
|
openWallet(listItem.getName(), true);
|
||||||
break;
|
} else if (id == R.id.action_info) {
|
||||||
case R.id.action_info:
|
showInfo(listItem.getName());
|
||||||
showInfo(listItem.name);
|
} else if (id == R.id.action_rename) {
|
||||||
break;
|
activityCallback.onWalletRename(listItem.getName());
|
||||||
case R.id.action_receive:
|
} else if (id == R.id.action_backup) {
|
||||||
showReceive(listItem.name);
|
activityCallback.onWalletBackup(listItem.getName());
|
||||||
break;
|
} else if (id == R.id.action_archive) {
|
||||||
case R.id.action_rename:
|
activityCallback.onWalletDelete(listItem.getName());
|
||||||
activityCallback.onWalletRename(listItem.name);
|
} else if (id == R.id.action_deletecache) {
|
||||||
break;
|
activityCallback.onWalletDeleteCache(listItem.getName());
|
||||||
case R.id.action_backup:
|
} else {
|
||||||
activityCallback.onWalletBackup(listItem.name);
|
return super.onContextItemSelected(item);
|
||||||
break;
|
|
||||||
case R.id.action_archive:
|
|
||||||
activityCallback.onWalletArchive(listItem.name);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return super.onContextItemSelected(item);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filterList() {
|
|
||||||
displayedList.clear();
|
|
||||||
String addressPrefix = WalletManager.getInstance().addressPrefix();
|
|
||||||
for (WalletManager.WalletInfo s : walletList) {
|
|
||||||
if (addressPrefix.indexOf(s.address.charAt(0)) >= 0) displayedList.add(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadList() {
|
public void loadList() {
|
||||||
Timber.d("loadList()");
|
Timber.d("loadList()");
|
||||||
WalletManager mgr = WalletManager.getInstance();
|
WalletManager mgr = WalletManager.getInstance();
|
||||||
List<WalletManager.WalletInfo> walletInfos =
|
|
||||||
mgr.findWallets(activityCallback.getStorageRoot());
|
|
||||||
walletList.clear();
|
walletList.clear();
|
||||||
walletList.addAll(walletInfos);
|
walletList.addAll(mgr.findWallets(activityCallback.getStorageRoot()));
|
||||||
filterList();
|
adapter.setInfos(walletList);
|
||||||
adapter.setInfos(displayedList);
|
|
||||||
|
|
||||||
// deal with Gunther & FAB animation
|
// deal with Gunther & FAB animation
|
||||||
if (displayedList.isEmpty()) {
|
if (walletList.isEmpty()) {
|
||||||
fab.startAnimation(fab_pulse);
|
fab.startAnimation(fab_pulse);
|
||||||
if (ivGunther.getDrawable() == null) {
|
if (ivGunther.getDrawable() == null) {
|
||||||
ivGunther.setImageResource(R.drawable.ic_emptygunther);
|
ivGunther.setImageResource(R.drawable.ic_emptygunther);
|
||||||
@@ -279,7 +257,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
|
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
.getAll().keySet();
|
.getAll().keySet();
|
||||||
for (WalletManager.WalletInfo s : walletList) {
|
for (WalletManager.WalletInfo s : walletList) {
|
||||||
removedWallets.remove(s.name);
|
removedWallets.remove(s.getName());
|
||||||
}
|
}
|
||||||
for (String name : removedWallets) {
|
for (String name : removedWallets) {
|
||||||
KeyStoreHelper.removeWalletUserPass(getActivity(), name);
|
KeyStoreHelper.removeWalletUserPass(getActivity(), name);
|
||||||
@@ -290,10 +268,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
activityCallback.onWalletDetails(name);
|
activityCallback.onWalletDetails(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showReceive(@NonNull String name) {
|
|
||||||
activityCallback.onWalletReceive(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -301,7 +275,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.list_menu, menu);
|
inflater.inflate(R.menu.list_menu, menu);
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
}
|
}
|
||||||
@@ -371,37 +345,29 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
int id = v.getId();
|
final int id = v.getId();
|
||||||
Timber.d("onClick %d/%d", id, R.id.fabLedger);
|
Timber.d("onClick %d/%d", id, R.id.fabLedger);
|
||||||
switch (id) {
|
if (id == R.id.fab) {
|
||||||
case R.id.fab:
|
animateFAB();
|
||||||
animateFAB();
|
} else if (id == R.id.fabNew) {
|
||||||
break;
|
fabScreen.setVisibility(View.INVISIBLE);
|
||||||
case R.id.fabNew:
|
isFabOpen = false;
|
||||||
fabScreen.setVisibility(View.INVISIBLE);
|
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
|
||||||
isFabOpen = false;
|
} else if (id == R.id.fabView) {
|
||||||
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
|
animateFAB();
|
||||||
break;
|
activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
|
||||||
case R.id.fabView:
|
} else if (id == R.id.fabKey) {
|
||||||
animateFAB();
|
animateFAB();
|
||||||
activityCallback.onAddWallet(GenerateFragment.TYPE_VIEWONLY);
|
activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
|
||||||
break;
|
} else if (id == R.id.fabSeed) {
|
||||||
case R.id.fabKey:
|
animateFAB();
|
||||||
animateFAB();
|
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
|
||||||
activityCallback.onAddWallet(GenerateFragment.TYPE_KEY);
|
} else if (id == R.id.fabLedger) {
|
||||||
break;
|
Timber.d("FAB_LEDGER");
|
||||||
case R.id.fabSeed:
|
animateFAB();
|
||||||
animateFAB();
|
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
|
||||||
activityCallback.onAddWallet(GenerateFragment.TYPE_SEED);
|
} else if (id == R.id.fabScreen) {
|
||||||
break;
|
animateFAB();
|
||||||
case R.id.fabLedger:
|
|
||||||
Timber.d("FAB_LEDGER");
|
|
||||||
animateFAB();
|
|
||||||
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
|
|
||||||
break;
|
|
||||||
case R.id.fabScreen:
|
|
||||||
animateFAB();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -228,24 +228,23 @@ public class NodeFragment extends Fragment
|
|||||||
|
|
||||||
// open up edit dialog
|
// open up edit dialog
|
||||||
@Override
|
@Override
|
||||||
public void onLongInteraction(final View view, final NodeInfo nodeItem) {
|
public boolean onLongInteraction(final View view, final NodeInfo nodeItem) {
|
||||||
Timber.d("onLongInteraction");
|
Timber.d("onLongInteraction");
|
||||||
EditDialog diag = createEditDialog(nodeItem);
|
EditDialog diag = createEditDialog(nodeItem);
|
||||||
if (diag != null) {
|
if (diag != null) {
|
||||||
diag.show();
|
diag.show();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
int id = v.getId();
|
int id = v.getId();
|
||||||
switch (id) {
|
if (id == R.id.fab) {
|
||||||
case R.id.fab:
|
EditDialog diag = createEditDialog(null);
|
||||||
EditDialog diag = createEditDialog(null);
|
if (diag != null) {
|
||||||
if (diag != null) {
|
diag.show();
|
||||||
diag.show();
|
}
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
|
||||||
|
public interface OnBlockUpdateListener {
|
||||||
|
void onBlockUpdate(final Wallet wallet);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
251
app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java
Normal file
251
app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017 m2049r
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.transition.MaterialElevationScale;
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
|
import com.m2049r.xmrwallet.layout.SubaddressInfoAdapter;
|
||||||
|
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
|
||||||
|
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||||
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class SubaddressFragment extends Fragment implements SubaddressInfoAdapter.OnInteractionListener,
|
||||||
|
View.OnClickListener, OnBlockUpdateListener {
|
||||||
|
static public final String KEY_MODE = "mode";
|
||||||
|
static public final String MODE_MANAGER = "manager";
|
||||||
|
|
||||||
|
private SubaddressInfoAdapter adapter;
|
||||||
|
|
||||||
|
private Listener activityCallback;
|
||||||
|
|
||||||
|
private Wallet wallet;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface Listener {
|
||||||
|
void onSubaddressSelected(Subaddress subaddress);
|
||||||
|
|
||||||
|
void setSubtitle(String title);
|
||||||
|
|
||||||
|
void setToolbarButton(int type);
|
||||||
|
|
||||||
|
void showSubaddress(View view, final int subaddressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ProgressListener {
|
||||||
|
void showProgressDialog(int msgId);
|
||||||
|
|
||||||
|
void showLedgerProgressDialog(int mode);
|
||||||
|
|
||||||
|
void dismissProgressDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgressListener progressCallback = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (context instanceof ProgressListener) {
|
||||||
|
progressCallback = (ProgressListener) context;
|
||||||
|
}
|
||||||
|
if (context instanceof Listener) {
|
||||||
|
activityCallback = (Listener) context;
|
||||||
|
} else {
|
||||||
|
throw new ClassCastException(context.toString()
|
||||||
|
+ " must implement Listener");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
Timber.d("onPause()");
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
activityCallback.setSubtitle(getString(R.string.subbaddress_title));
|
||||||
|
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
Timber.d("onCreateView");
|
||||||
|
|
||||||
|
final Bundle b = getArguments();
|
||||||
|
managerMode = ((b != null) && (MODE_MANAGER.equals(b.getString(KEY_MODE))));
|
||||||
|
|
||||||
|
View view = inflater.inflate(R.layout.fragment_subaddress, container, false);
|
||||||
|
|
||||||
|
final MaterialElevationScale exitTransition = new MaterialElevationScale(false);
|
||||||
|
exitTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
setExitTransition(exitTransition);
|
||||||
|
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
|
||||||
|
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
setReenterTransition(reenterTransition);
|
||||||
|
|
||||||
|
view.findViewById(R.id.fab).setOnClickListener(this);
|
||||||
|
|
||||||
|
if (managerMode) {
|
||||||
|
view.findViewById(R.id.tvInstruction).setVisibility(View.GONE);
|
||||||
|
view.findViewById(R.id.tvHint).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
final RecyclerView list = view.findViewById(R.id.list);
|
||||||
|
adapter = new SubaddressInfoAdapter(getActivity(), this);
|
||||||
|
list.setAdapter(adapter);
|
||||||
|
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||||
|
@Override
|
||||||
|
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||||
|
list.scrollToPosition(positionStart);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Helper.hideKeyboard(getActivity());
|
||||||
|
|
||||||
|
wallet = WalletManager.getInstance().getWallet();
|
||||||
|
|
||||||
|
loadList();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
postponeEnterTransition();
|
||||||
|
view.getViewTreeObserver().addOnPreDrawListener(() -> {
|
||||||
|
startPostponedEnterTransition();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadList() {
|
||||||
|
Timber.d("loadList()");
|
||||||
|
final int numSubaddresses = wallet.getNumSubaddresses();
|
||||||
|
final List<Subaddress> list = new ArrayList<>();
|
||||||
|
for (int i = 0; i < numSubaddresses; i++) {
|
||||||
|
list.add(wallet.getSubaddressObject(i));
|
||||||
|
}
|
||||||
|
adapter.setInfos(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlockUpdate(Wallet wallet) {
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
int id = v.getId();
|
||||||
|
if (id == R.id.fab) {
|
||||||
|
getNewSubaddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lastUsedSubaddress() {
|
||||||
|
int lastUsedSubaddress = 0;
|
||||||
|
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
||||||
|
if (info.addressIndex > lastUsedSubaddress)
|
||||||
|
lastUsedSubaddress = info.addressIndex;
|
||||||
|
}
|
||||||
|
return lastUsedSubaddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getNewSubaddress() {
|
||||||
|
final int maxSubaddresses = lastUsedSubaddress() + wallet.getDeviceType().getSubaddressLookahead();
|
||||||
|
if (wallet.getNumSubaddresses() < maxSubaddresses)
|
||||||
|
new AsyncSubaddress().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
|
||||||
|
else
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.max_subaddress_warning), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
private class AsyncSubaddress extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
boolean dialogOpened = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) {
|
||||||
|
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
|
||||||
|
dialogOpened = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
if (params.length != 0) return false;
|
||||||
|
wallet.getNewSubaddress();
|
||||||
|
wallet.store();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
if (dialogOpened)
|
||||||
|
progressCallback.dismissProgressDialog();
|
||||||
|
if (!isAdded()) // never mind then
|
||||||
|
return;
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean managerMode = false;
|
||||||
|
|
||||||
|
// Callbacks from SubaddressInfoAdapter
|
||||||
|
@Override
|
||||||
|
public void onInteraction(final View view, final Subaddress subaddress) {
|
||||||
|
if (managerMode)
|
||||||
|
activityCallback.showSubaddress(view, subaddress.getAddressIndex());
|
||||||
|
else
|
||||||
|
activityCallback.onSubaddressSelected(subaddress); // also closes the fragment with onBackpressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongInteraction(View view, Subaddress subaddress) {
|
||||||
|
activityCallback.showSubaddress(view, subaddress.getAddressIndex());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017 m2049r
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
import com.google.android.material.transition.MaterialContainerTransform;
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
|
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
|
||||||
|
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
import com.m2049r.xmrwallet.util.ThemeHelper;
|
||||||
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class SubaddressInfoFragment extends Fragment
|
||||||
|
implements TransactionInfoAdapter.OnInteractionListener, OnBlockUpdateListener {
|
||||||
|
private TransactionInfoAdapter adapter;
|
||||||
|
|
||||||
|
private Subaddress subaddress;
|
||||||
|
|
||||||
|
private TextInputLayout etName;
|
||||||
|
private TextView tvAddress;
|
||||||
|
private TextView tvTxLabel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_subaddressinfo, container, false);
|
||||||
|
|
||||||
|
etName = view.findViewById(R.id.etName);
|
||||||
|
tvAddress = view.findViewById(R.id.tvAddress);
|
||||||
|
tvTxLabel = view.findViewById(R.id.tvTxLabel);
|
||||||
|
|
||||||
|
final RecyclerView list = view.findViewById(R.id.list);
|
||||||
|
adapter = new TransactionInfoAdapter(getActivity(), this);
|
||||||
|
list.setAdapter(adapter);
|
||||||
|
|
||||||
|
final Wallet wallet = activityCallback.getWallet();
|
||||||
|
|
||||||
|
Bundle b = getArguments();
|
||||||
|
final int subaddressIndex = b.getInt("subaddressIndex");
|
||||||
|
subaddress = wallet.getSubaddressObject(subaddressIndex);
|
||||||
|
|
||||||
|
etName.getEditText().setText(subaddress.getDisplayLabel());
|
||||||
|
tvAddress.setText(getContext().getString(R.string.subbaddress_info_subtitle,
|
||||||
|
subaddress.getAddressIndex(), subaddress.getSquashedAddress()));
|
||||||
|
|
||||||
|
etName.getEditText().setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
if (!hasFocus) {
|
||||||
|
wallet.setSubaddressLabel(subaddressIndex, etName.getEditText().getText().toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
etName.getEditText().setOnEditorActionListener((v, actionId, event) -> {
|
||||||
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||||
|
Helper.hideKeyboard(getActivity());
|
||||||
|
wallet.setSubaddressLabel(subaddressIndex, etName.getEditText().getText().toString());
|
||||||
|
onRefreshed(wallet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
onRefreshed(wallet);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
final MaterialContainerTransform transform = new MaterialContainerTransform();
|
||||||
|
transform.setDrawingViewId(R.id.fragment_container);
|
||||||
|
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
|
||||||
|
setSharedElementEnterTransition(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRefreshed(final Wallet wallet) {
|
||||||
|
Timber.d("onRefreshed");
|
||||||
|
List<TransactionInfo> list = new ArrayList<>();
|
||||||
|
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
||||||
|
if (info.addressIndex == subaddress.getAddressIndex())
|
||||||
|
list.add(info);
|
||||||
|
}
|
||||||
|
adapter.setInfos(list);
|
||||||
|
if (list.isEmpty())
|
||||||
|
tvTxLabel.setText(R.string.subaddress_notx_label);
|
||||||
|
else
|
||||||
|
tvTxLabel.setText(R.string.subaddress_tx_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlockUpdate(Wallet wallet) {
|
||||||
|
onRefreshed(wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callbacks from TransactionInfoAdapter
|
||||||
|
@Override
|
||||||
|
public void onInteraction(final View view, final TransactionInfo infoItem) {
|
||||||
|
activityCallback.onTxDetailsRequest(view, infoItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
Listener activityCallback;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface Listener {
|
||||||
|
void onTxDetailsRequest(View view, TransactionInfo info);
|
||||||
|
|
||||||
|
Wallet getWallet();
|
||||||
|
|
||||||
|
void setToolbarButton(int type);
|
||||||
|
|
||||||
|
void setTitle(String title, String subtitle);
|
||||||
|
|
||||||
|
void setSubtitle(String subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (context instanceof Listener) {
|
||||||
|
this.activityCallback = (Listener) context;
|
||||||
|
} else {
|
||||||
|
throw new ClassCastException(context.toString()
|
||||||
|
+ " must implement Listener");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
Timber.d("onResume()");
|
||||||
|
activityCallback.setSubtitle(getString(R.string.subbaddress_title));
|
||||||
|
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
}
|
@@ -22,7 +22,9 @@ import android.content.Intent;
|
|||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Html;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.text.Spanned;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -37,12 +39,14 @@ import androidx.core.content.ContextCompat;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.google.android.material.transition.MaterialContainerTransform;
|
import com.google.android.material.transition.MaterialContainerTransform;
|
||||||
|
import com.google.android.material.transition.MaterialElevationScale;
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
import com.m2049r.xmrwallet.data.UserNotes;
|
import com.m2049r.xmrwallet.data.UserNotes;
|
||||||
import com.m2049r.xmrwallet.model.TransactionInfo;
|
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||||
import com.m2049r.xmrwallet.model.Transfer;
|
import com.m2049r.xmrwallet.model.Transfer;
|
||||||
import com.m2049r.xmrwallet.model.Wallet;
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.util.ThemeHelper;
|
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
import com.m2049r.xmrwallet.util.ThemeHelper;
|
||||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@@ -52,6 +56,8 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class TxFragment extends Fragment {
|
public class TxFragment extends Fragment {
|
||||||
|
|
||||||
static public final String ARG_INFO = "info";
|
static public final String ARG_INFO = "info";
|
||||||
@@ -88,11 +94,16 @@ public class TxFragment extends Fragment {
|
|||||||
private ImageView tvXmrToLogo;
|
private ImageView tvXmrToLogo;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
Bundle savedInstanceState) {
|
|
||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
|
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
|
||||||
|
|
||||||
|
final MaterialElevationScale exitTransition = new MaterialElevationScale(false);
|
||||||
|
exitTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
setExitTransition(exitTransition);
|
||||||
|
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
|
||||||
|
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
setReenterTransition(reenterTransition);
|
||||||
|
|
||||||
cvXmrTo = view.findViewById(R.id.cvXmrTo);
|
cvXmrTo = view.findViewById(R.id.cvXmrTo);
|
||||||
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
|
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
|
||||||
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
|
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
|
||||||
@@ -121,9 +132,8 @@ public class TxFragment extends Fragment {
|
|||||||
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
|
||||||
});
|
});
|
||||||
|
|
||||||
Bundle args = getArguments();
|
info = getArguments().getParcelable(ARG_INFO);
|
||||||
TransactionInfo info = args.getParcelable(ARG_INFO);
|
show();
|
||||||
show(info);
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +202,7 @@ public class TxFragment extends Fragment {
|
|||||||
TransactionInfo info = null;
|
TransactionInfo info = null;
|
||||||
UserNotes userNotes = null;
|
UserNotes userNotes = null;
|
||||||
|
|
||||||
void loadNotes(TransactionInfo info) {
|
void loadNotes() {
|
||||||
if ((userNotes == null) || (info.notes == null)) {
|
if ((userNotes == null) || (info.notes == null)) {
|
||||||
info.notes = activityCallback.getTxNotes(info.hash);
|
info.notes = activityCallback.getTxNotes(info.hash);
|
||||||
}
|
}
|
||||||
@@ -205,19 +215,28 @@ public class TxFragment extends Fragment {
|
|||||||
tvTxFee.setTextColor(clr);
|
tvTxFee.setTextColor(clr);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void show(TransactionInfo info) {
|
private void showSubaddressLabel() {
|
||||||
|
final Subaddress subaddress = activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex);
|
||||||
|
final Context ctx = getContext();
|
||||||
|
Spanned label = Html.fromHtml(ctx.getString(R.string.tx_account_formatted,
|
||||||
|
info.accountIndex, info.addressIndex,
|
||||||
|
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoGreen) & 0xFFFFFF),
|
||||||
|
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoBackground) & 0xFFFFFF),
|
||||||
|
subaddress.getDisplayLabel()));
|
||||||
|
tvAccount.setText(label);
|
||||||
|
tvAccount.setOnClickListener(v -> activityCallback.showSubaddress(v, info.addressIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void show() {
|
||||||
if (info.txKey == null) {
|
if (info.txKey == null) {
|
||||||
info.txKey = activityCallback.getTxKey(info.hash);
|
info.txKey = activityCallback.getTxKey(info.hash);
|
||||||
}
|
}
|
||||||
if (info.address == null) {
|
if (info.address == null) {
|
||||||
info.address = activityCallback.getTxAddress(info.account, info.subaddress);
|
info.address = activityCallback.getTxAddress(info.accountIndex, info.addressIndex);
|
||||||
}
|
}
|
||||||
loadNotes(info);
|
loadNotes();
|
||||||
|
|
||||||
activityCallback.setSubtitle(getString(R.string.tx_title));
|
showSubaddressLabel();
|
||||||
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
|
||||||
|
|
||||||
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
|
|
||||||
tvAddress.setText(info.address);
|
tvAddress.setText(info.address);
|
||||||
|
|
||||||
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
||||||
@@ -256,8 +275,8 @@ public class TxFragment extends Fragment {
|
|||||||
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_minus));
|
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_minus));
|
||||||
}
|
}
|
||||||
Set<String> destinations = new HashSet<>();
|
Set<String> destinations = new HashSet<>();
|
||||||
StringBuffer sb = new StringBuffer();
|
StringBuilder sb = new StringBuilder();
|
||||||
StringBuffer dstSb = new StringBuffer();
|
StringBuilder dstSb = new StringBuilder();
|
||||||
if (info.transfers != null) {
|
if (info.transfers != null) {
|
||||||
boolean newline = false;
|
boolean newline = false;
|
||||||
for (Transfer transfer : info.transfers) {
|
for (Transfer transfer : info.transfers) {
|
||||||
@@ -281,14 +300,12 @@ public class TxFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sb.append("-");
|
sb.append("-");
|
||||||
dstSb.append(info.direction ==
|
dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ?
|
||||||
TransactionInfo.Direction.Direction_In ?
|
activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex).getAddress() :
|
||||||
activityCallback.getWalletSubaddress(info.account, info.subaddress) :
|
|
||||||
"-");
|
"-");
|
||||||
}
|
}
|
||||||
tvTxTransfers.setText(sb.toString());
|
tvTxTransfers.setText(sb.toString());
|
||||||
tvDestination.setText(dstSb.toString());
|
tvDestination.setText(dstSb.toString());
|
||||||
this.info = info;
|
|
||||||
showBtcInfo();
|
showBtcInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,7 +351,7 @@ public class TxFragment extends Fragment {
|
|||||||
final MaterialContainerTransform transform = new MaterialContainerTransform();
|
final MaterialContainerTransform transform = new MaterialContainerTransform();
|
||||||
transform.setDrawingViewId(R.id.fragment_container);
|
transform.setDrawingViewId(R.id.fragment_container);
|
||||||
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), R.attr.colorSurface));
|
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
|
||||||
setSharedElementEnterTransition(transform);
|
setSharedElementEnterTransition(transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,7 +364,7 @@ public class TxFragment extends Fragment {
|
|||||||
Listener activityCallback;
|
Listener activityCallback;
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
String getWalletSubaddress(int accountIndex, int subaddressIndex);
|
Subaddress getWalletSubaddress(int accountIndex, int subaddressIndex);
|
||||||
|
|
||||||
String getTxKey(String hash);
|
String getTxKey(String hash);
|
||||||
|
|
||||||
@@ -361,6 +378,8 @@ public class TxFragment extends Fragment {
|
|||||||
|
|
||||||
void setSubtitle(String subtitle);
|
void setSubtitle(String subtitle);
|
||||||
|
|
||||||
|
void showSubaddress(View view, final int subaddressIndex);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -385,4 +404,13 @@ public class TxFragment extends Fragment {
|
|||||||
Helper.hideKeyboard(getActivity());
|
Helper.hideKeyboard(getActivity());
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
Timber.d("onResume()");
|
||||||
|
activityCallback.setSubtitle(getString(R.string.tx_title));
|
||||||
|
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||||
|
showSubaddressLabel();
|
||||||
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -79,6 +79,7 @@ public class WalletFragment extends Fragment
|
|||||||
private Button bSend;
|
private Button bSend;
|
||||||
private ImageView ivStreetGunther;
|
private ImageView ivStreetGunther;
|
||||||
private Drawable streetGunther = null;
|
private Drawable streetGunther = null;
|
||||||
|
RecyclerView txlist;
|
||||||
|
|
||||||
private Spinner sCurrency;
|
private Spinner sCurrency;
|
||||||
|
|
||||||
@@ -106,13 +107,6 @@ public class WalletFragment extends Fragment
|
|||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_wallet, container, false);
|
View view = inflater.inflate(R.layout.fragment_wallet, container, false);
|
||||||
|
|
||||||
final MaterialElevationScale exitTransition = new MaterialElevationScale(false);
|
|
||||||
exitTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
|
||||||
setExitTransition(exitTransition);
|
|
||||||
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
|
|
||||||
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
|
||||||
setReenterTransition(reenterTransition);
|
|
||||||
|
|
||||||
ivStreetGunther = view.findViewById(R.id.ivStreetGunther);
|
ivStreetGunther = view.findViewById(R.id.ivStreetGunther);
|
||||||
tvStreetView = view.findViewById(R.id.tvStreetView);
|
tvStreetView = view.findViewById(R.id.tvStreetView);
|
||||||
llBalance = view.findViewById(R.id.llBalance);
|
llBalance = view.findViewById(R.id.llBalance);
|
||||||
@@ -132,7 +126,8 @@ public class WalletFragment extends Fragment
|
|||||||
sCurrency = view.findViewById(R.id.sCurrency);
|
sCurrency = view.findViewById(R.id.sCurrency);
|
||||||
List<String> currencies = new ArrayList<>();
|
List<String> currencies = new ArrayList<>();
|
||||||
currencies.add(Helper.BASE_CRYPTO);
|
currencies.add(Helper.BASE_CRYPTO);
|
||||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
if (Helper.SHOW_EXCHANGERATES)
|
||||||
|
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||||
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()), R.layout.item_spinner_balance, currencies);
|
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()), R.layout.item_spinner_balance, currencies);
|
||||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
sCurrency.setAdapter(spinnerAdapter);
|
sCurrency.setAdapter(spinnerAdapter);
|
||||||
@@ -140,13 +135,19 @@ public class WalletFragment extends Fragment
|
|||||||
bSend = view.findViewById(R.id.bSend);
|
bSend = view.findViewById(R.id.bSend);
|
||||||
bReceive = view.findViewById(R.id.bReceive);
|
bReceive = view.findViewById(R.id.bReceive);
|
||||||
|
|
||||||
RecyclerView recyclerView = view.findViewById(R.id.list);
|
txlist = view.findViewById(R.id.list);
|
||||||
|
|
||||||
adapter = new TransactionInfoAdapter(getActivity(), this);
|
adapter = new TransactionInfoAdapter(getActivity(), this);
|
||||||
recyclerView.setAdapter(adapter);
|
txlist.setAdapter(adapter);
|
||||||
|
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||||
|
@Override
|
||||||
|
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||||
|
if ((positionStart == 0) && (txlist.computeVerticalScrollOffset() == 0))
|
||||||
|
txlist.scrollToPosition(positionStart);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
SwipeableRecyclerViewTouchListener swipeTouchListener =
|
txlist.addOnItemTouchListener(
|
||||||
new SwipeableRecyclerViewTouchListener(recyclerView,
|
new SwipeableRecyclerViewTouchListener(txlist,
|
||||||
new SwipeableRecyclerViewTouchListener.SwipeListener() {
|
new SwipeableRecyclerViewTouchListener.SwipeListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean canSwipeLeft(int position) {
|
public boolean canSwipeLeft(int position) {
|
||||||
@@ -173,10 +174,7 @@ public class WalletFragment extends Fragment
|
|||||||
adapter.removeItem(position);
|
adapter.removeItem(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
recyclerView.addOnItemTouchListener(swipeTouchListener);
|
|
||||||
|
|
||||||
|
|
||||||
bSend.setOnClickListener(v -> activityCallback.onSendRequest(v));
|
bSend.setOnClickListener(v -> activityCallback.onSendRequest(v));
|
||||||
bReceive.setOnClickListener(v -> activityCallback.onWalletReceive(v));
|
bReceive.setOnClickListener(v -> activityCallback.onWalletReceive(v));
|
||||||
@@ -338,24 +336,34 @@ public class WalletFragment extends Fragment
|
|||||||
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
|
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
|
||||||
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
setReenterTransition(reenterTransition);
|
setReenterTransition(reenterTransition);
|
||||||
|
|
||||||
activityCallback.onTxDetailsRequest(view, infoItem);
|
activityCallback.onTxDetailsRequest(view, infoItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// called from activity
|
// called from activity
|
||||||
|
|
||||||
|
|
||||||
|
// if account index has changed scroll to top?
|
||||||
|
private int accountIndex = 0;
|
||||||
|
|
||||||
public void onRefreshed(final Wallet wallet, final boolean full) {
|
public void onRefreshed(final Wallet wallet, final boolean full) {
|
||||||
Timber.d("onRefreshed(%b)", full);
|
Timber.d("onRefreshed(%b)", full);
|
||||||
|
|
||||||
if (full) {
|
if (full) {
|
||||||
List<TransactionInfo> list = new ArrayList<>();
|
List<TransactionInfo> list = new ArrayList<>();
|
||||||
final long streetHeight = activityCallback.getStreetModeHeight();
|
final long streetHeight = activityCallback.getStreetModeHeight();
|
||||||
Timber.d("StreetHeight=%d", streetHeight);
|
Timber.d("StreetHeight=%d", streetHeight);
|
||||||
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
||||||
Timber.d("TxHeight=%d", info.blockheight);
|
Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel);
|
||||||
if ((info.isPending || (info.blockheight >= streetHeight))
|
if ((info.isPending || (info.blockheight >= streetHeight))
|
||||||
&& !dismissedTransactions.contains(info.hash))
|
&& !dismissedTransactions.contains(info.hash))
|
||||||
list.add(info);
|
list.add(info);
|
||||||
}
|
}
|
||||||
adapter.setInfos(list);
|
adapter.setInfos(list);
|
||||||
|
if (accountIndex != wallet.getAccountIndex()) {
|
||||||
|
accountIndex = wallet.getAccountIndex();
|
||||||
|
txlist.scrollToPosition(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateStatus(wallet);
|
updateStatus(wallet);
|
||||||
}
|
}
|
||||||
@@ -522,9 +530,10 @@ public class WalletFragment extends Fragment
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
setExitTransition(null);
|
||||||
|
setReenterTransition(null);
|
||||||
Timber.d("onResume()");
|
Timber.d("onResume()");
|
||||||
activityCallback.setTitle(walletTitle, walletSubtitle);
|
activityCallback.setTitle(walletTitle, walletSubtitle);
|
||||||
//activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE); // TODO: Close button somewhere else
|
|
||||||
activityCallback.setToolbarButton(Toolbar.BUTTON_NONE);
|
activityCallback.setToolbarButton(Toolbar.BUTTON_NONE);
|
||||||
setProgress(syncProgress);
|
setProgress(syncProgress);
|
||||||
setProgress(syncText);
|
setProgress(syncText);
|
||||||
|
63
app/src/main/java/com/m2049r/xmrwallet/data/Subaddress.java
Normal file
63
app/src/main/java/com/m2049r/xmrwallet/data/Subaddress.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public class Subaddress implements Comparable<Subaddress> {
|
||||||
|
@Getter
|
||||||
|
final private int accountIndex;
|
||||||
|
@Getter
|
||||||
|
final private int addressIndex;
|
||||||
|
@Getter
|
||||||
|
final private String address;
|
||||||
|
@Getter
|
||||||
|
private final String label;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private long amount;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Subaddress another) { // newer is <
|
||||||
|
final int compareAccountIndex = another.accountIndex - accountIndex;
|
||||||
|
if (compareAccountIndex == 0)
|
||||||
|
return another.addressIndex - addressIndex;
|
||||||
|
return compareAccountIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSquashedAddress() {
|
||||||
|
return address.substring(0, 8) + "…" + address.substring(address.length() - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Pattern DEFAULT_LABEL_FORMATTER = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$");
|
||||||
|
|
||||||
|
public String getDisplayLabel() {
|
||||||
|
if (label.isEmpty() || (DEFAULT_LABEL_FORMATTER.matcher(label).matches()))
|
||||||
|
return ("#" + addressIndex);
|
||||||
|
else
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
@@ -21,6 +21,7 @@ import android.os.Parcelable;
|
|||||||
|
|
||||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||||
import com.m2049r.xmrwallet.model.Wallet;
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
||||||
public class TxData implements Parcelable {
|
public class TxData implements Parcelable {
|
||||||
@@ -54,7 +55,7 @@ public class TxData implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double getAmountAsDouble() {
|
public double getAmountAsDouble() {
|
||||||
return 1.0 * amount / 1000000000000L;
|
return 1.0 * amount / Helper.ONE_XMR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMixin() {
|
public int getMixin() {
|
||||||
|
@@ -117,32 +117,34 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_send_address, container, false);
|
View view = inflater.inflate(R.layout.fragment_send_address, container, false);
|
||||||
|
|
||||||
tvXmrTo = view.findViewById(R.id.tvXmrTo);
|
if (Helper.ALLOW_SHIFT) {
|
||||||
|
tvXmrTo = view.findViewById(R.id.tvXmrTo);
|
||||||
ibCrypto = new HashMap<>();
|
ibCrypto = new HashMap<>();
|
||||||
for (Crypto crypto : Crypto.values()) {
|
for (Crypto crypto : Crypto.values()) {
|
||||||
final ImageButton button = view.findViewById(crypto.getButtonId());
|
final ImageButton button = view.findViewById(crypto.getButtonId());
|
||||||
ibCrypto.put(crypto, button);
|
ibCrypto.put(crypto, button);
|
||||||
button.setOnClickListener(v -> {
|
button.setOnClickListener(v -> {
|
||||||
if (possibleCryptos.contains(crypto)) {
|
if (possibleCryptos.contains(crypto)) {
|
||||||
selectedCrypto = crypto;
|
selectedCrypto = crypto;
|
||||||
updateCryptoButtons(false);
|
updateCryptoButtons(false);
|
||||||
} else {
|
|
||||||
// show help what to do:
|
|
||||||
if (button.getId() != R.id.ibXMR) {
|
|
||||||
final String name = getResources().getStringArray(R.array.cryptos)[crypto.ordinal()];
|
|
||||||
final String symbol = getCryptoForButton(button).getSymbol();
|
|
||||||
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help, name, symbol)));
|
|
||||||
tvXmrTo.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
} else {
|
||||||
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr)));
|
// show help what to do:
|
||||||
tvXmrTo.setVisibility(View.VISIBLE);
|
if (button.getId() != R.id.ibXMR) {
|
||||||
|
final String name = getResources().getStringArray(R.array.cryptos)[crypto.ordinal()];
|
||||||
|
final String symbol = getCryptoForButton(button).getSymbol();
|
||||||
|
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help, name, symbol)));
|
||||||
|
tvXmrTo.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr)));
|
||||||
|
tvXmrTo.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
updateCryptoButtons(true);
|
||||||
|
} else {
|
||||||
|
view.findViewById(R.id.llExchange).setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
updateCryptoButtons(true);
|
|
||||||
|
|
||||||
etAddress = view.findViewById(R.id.etAddress);
|
etAddress = view.findViewById(R.id.etAddress);
|
||||||
etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
@@ -181,6 +183,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
selectedCrypto = Crypto.XMR;
|
selectedCrypto = Crypto.XMR;
|
||||||
sendListener.setMode(SendFragment.Mode.XMR);
|
sendListener.setMode(SendFragment.Mode.XMR);
|
||||||
}
|
}
|
||||||
|
if (!Helper.ALLOW_SHIFT) return;
|
||||||
if ((selectedCrypto == null) && isEthAddress(address)) {
|
if ((selectedCrypto == null) && isEthAddress(address)) {
|
||||||
Timber.d("isEthAddress");
|
Timber.d("isEthAddress");
|
||||||
possibleCryptos.add(Crypto.ETH);
|
possibleCryptos.add(Crypto.ETH);
|
||||||
@@ -285,6 +288,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateCryptoButtons(boolean noAddress) {
|
private void updateCryptoButtons(boolean noAddress) {
|
||||||
|
if (!Helper.ALLOW_SHIFT) return;
|
||||||
for (Crypto crypto : Crypto.values()) {
|
for (Crypto crypto : Crypto.values()) {
|
||||||
if (crypto == selectedCrypto) {
|
if (crypto == selectedCrypto) {
|
||||||
selectedCrypto(crypto);
|
selectedCrypto(crypto);
|
||||||
@@ -447,7 +451,11 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
BarcodeData barcodeData = sendListener.getBarcodeData();
|
BarcodeData barcodeData = sendListener.getBarcodeData();
|
||||||
if (barcodeData != null) {
|
if (barcodeData != null) {
|
||||||
Timber.d("GOT DATA");
|
Timber.d("GOT DATA");
|
||||||
|
if (!Helper.ALLOW_SHIFT && (barcodeData.asset != Crypto.XMR)) {
|
||||||
|
Timber.d("BUT ONLY XMR SUPPORTED");
|
||||||
|
barcodeData = null;
|
||||||
|
sendListener.setBarcodeData(barcodeData);
|
||||||
|
}
|
||||||
if (barcodeData.address != null) {
|
if (barcodeData.address != null) {
|
||||||
etAddress.getEditText().setText(barcodeData.address);
|
etAddress.getEditText().setText(barcodeData.address);
|
||||||
possibleCryptos.clear();
|
possibleCryptos.clear();
|
||||||
@@ -458,7 +466,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
|||||||
possibleCryptos.add(barcodeData.asset);
|
possibleCryptos.add(barcodeData.asset);
|
||||||
selectedCrypto = barcodeData.asset;
|
selectedCrypto = barcodeData.asset;
|
||||||
}
|
}
|
||||||
updateCryptoButtons(false);
|
if (Helper.ALLOW_SHIFT)
|
||||||
|
updateCryptoButtons(false);
|
||||||
if (checkAddress()) {
|
if (checkAddress()) {
|
||||||
if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC)
|
if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC)
|
||||||
etAddress.setError(getString(R.string.send_address_no_dnssec));
|
etAddress.setError(getString(R.string.send_address_no_dnssec));
|
||||||
|
@@ -139,7 +139,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
|
|||||||
Timber.d("onResumeFragment()");
|
Timber.d("onResumeFragment()");
|
||||||
Helper.showKeyboard(getActivity());
|
Helper.showKeyboard(getActivity());
|
||||||
final long funds = getTotalFunds();
|
final long funds = getTotalFunds();
|
||||||
maxFunds = 1.0 * funds / 1000000000000L;
|
maxFunds = 1.0 * funds / Helper.ONE_XMR;
|
||||||
if (!sendListener.getActivityCallback().isStreetMode()) {
|
if (!sendListener.getActivityCallback().isStreetMode()) {
|
||||||
tvFunds.setText(getString(R.string.send_available,
|
tvFunds.setText(getString(R.string.send_available,
|
||||||
Wallet.getDisplayAmount(funds)));
|
Wallet.getDisplayAmount(funds)));
|
||||||
|
@@ -34,6 +34,7 @@ import com.m2049r.xmrwallet.service.shift.ShiftException;
|
|||||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
|
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
|
||||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
|
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
|
||||||
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
|
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
import com.m2049r.xmrwallet.util.OkHttpHelper;
|
import com.m2049r.xmrwallet.util.OkHttpHelper;
|
||||||
import com.m2049r.xmrwallet.util.ServiceHelper;
|
import com.m2049r.xmrwallet.util.ServiceHelper;
|
||||||
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
|
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
|
||||||
@@ -179,7 +180,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
|||||||
tvXmrToParms.setText(xmrParmText);
|
tvXmrToParms.setText(xmrParmText);
|
||||||
|
|
||||||
final long funds = getTotalFunds();
|
final long funds = getTotalFunds();
|
||||||
double availableXmr = 1.0 * funds / 1000000000000L;
|
double availableXmr = 1.0 * funds / Helper.ONE_XMR;
|
||||||
|
|
||||||
String availBtcString;
|
String availBtcString;
|
||||||
String availXmrString;
|
String availXmrString;
|
||||||
|
@@ -66,7 +66,7 @@ public class SendFragment extends Fragment
|
|||||||
SendSuccessWizardFragment.Listener,
|
SendSuccessWizardFragment.Listener,
|
||||||
OnBackPressedListener, OnUriScannedListener {
|
OnBackPressedListener, OnUriScannedListener {
|
||||||
|
|
||||||
final static public int MIXIN = 10;
|
final static public int MIXIN = 0;
|
||||||
|
|
||||||
private Listener activityCallback;
|
private Listener activityCallback;
|
||||||
|
|
||||||
@@ -547,7 +547,7 @@ public class SendFragment extends Fragment
|
|||||||
final MaterialContainerTransform transform = new MaterialContainerTransform();
|
final MaterialContainerTransform transform = new MaterialContainerTransform();
|
||||||
transform.setDrawingViewId(R.id.fragment_container);
|
transform.setDrawingViewId(R.id.fragment_container);
|
||||||
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
transform.setAllContainerColors(ThemeHelper.getThemedColor(Objects.requireNonNull(getContext()), R.attr.colorSurface));
|
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
|
||||||
setSharedElementEnterTransition(transform);
|
setSharedElementEnterTransition(transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -48,7 +48,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
|||||||
public interface OnInteractionListener {
|
public interface OnInteractionListener {
|
||||||
void onInteraction(View view, NodeInfo item);
|
void onInteraction(View view, NodeInfo item);
|
||||||
|
|
||||||
void onLongInteraction(View view, NodeInfo item);
|
boolean onLongInteraction(View view, NodeInfo item);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final List<NodeInfo> nodeItems = new ArrayList<>();
|
private final List<NodeInfo> nodeItems = new ArrayList<>();
|
||||||
@@ -213,10 +213,10 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
|
|||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
int position = getAdapterPosition(); // gets item position
|
int position = getAdapterPosition(); // gets item position
|
||||||
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
|
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
|
||||||
listener.onLongInteraction(view, nodeItems.get(position));
|
return listener.onLongInteraction(view, nodeItems.get(position));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017 m2049r
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.m2049r.xmrwallet.layout;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.R;
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class SubaddressInfoAdapter extends RecyclerView.Adapter<SubaddressInfoAdapter.ViewHolder> {
|
||||||
|
public interface OnInteractionListener {
|
||||||
|
void onInteraction(View view, Subaddress item);
|
||||||
|
|
||||||
|
boolean onLongInteraction(View view, Subaddress item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Subaddress> items;
|
||||||
|
private final OnInteractionListener listener;
|
||||||
|
|
||||||
|
Context context;
|
||||||
|
|
||||||
|
public SubaddressInfoAdapter(Context context, OnInteractionListener listener) {
|
||||||
|
this.context = context;
|
||||||
|
this.items = new ArrayList<>();
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SubaddressInfoDiff extends DiffCallback<Subaddress> {
|
||||||
|
|
||||||
|
public SubaddressInfoDiff(List<Subaddress> oldList, List<Subaddress> newList) {
|
||||||
|
super(oldList, newList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||||
|
return mOldList.get(oldItemPosition).getAddress().equals(mNewList.get(newItemPosition).getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||||
|
return mOldList.get(oldItemPosition).equals(mNewList.get(newItemPosition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_subaddress, parent, false);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||||
|
holder.bind(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return items.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subaddress getItem(int position) {
|
||||||
|
return items.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInfos(List<Subaddress> newItems) {
|
||||||
|
if (newItems == null) {
|
||||||
|
newItems = new ArrayList<>();
|
||||||
|
Timber.d("setInfos null");
|
||||||
|
} else {
|
||||||
|
Timber.d("setInfos %s", newItems.size());
|
||||||
|
}
|
||||||
|
Collections.sort(newItems);
|
||||||
|
final DiffCallback<Subaddress> diffCallback = new SubaddressInfoDiff(items, newItems);
|
||||||
|
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
|
||||||
|
items.clear();
|
||||||
|
items.addAll(newItems);
|
||||||
|
diffResult.dispatchUpdatesTo(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
|
||||||
|
final TextView tvName;
|
||||||
|
final TextView tvAddress;
|
||||||
|
final TextView tvAmount;
|
||||||
|
Subaddress item;
|
||||||
|
|
||||||
|
ViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
tvName = itemView.findViewById(R.id.tvName);
|
||||||
|
tvAddress = itemView.findViewById(R.id.tvAddress);
|
||||||
|
tvAmount = itemView.findViewById(R.id.tx_amount);
|
||||||
|
itemView.setOnClickListener(this);
|
||||||
|
itemView.setOnLongClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void bind(int position) {
|
||||||
|
item = getItem(position);
|
||||||
|
itemView.setTransitionName(context.getString(R.string.subaddress_item_transition_name, item.getAddressIndex()));
|
||||||
|
|
||||||
|
final String label = item.getDisplayLabel();
|
||||||
|
final String address = context.getString(R.string.subbaddress_info_subtitle,
|
||||||
|
item.getAddressIndex(), item.getSquashedAddress());
|
||||||
|
tvName.setText(label.isEmpty() ? address : label);
|
||||||
|
tvAddress.setText(address);
|
||||||
|
final long amount = item.getAmount();
|
||||||
|
if (amount > 0)
|
||||||
|
tvAmount.setText(context.getString(R.string.tx_list_amount_positive,
|
||||||
|
Helper.getDisplayAmount(amount, Helper.DISPLAY_DIGITS_INFO)));
|
||||||
|
else
|
||||||
|
tvAmount.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
if (listener != null) {
|
||||||
|
int position = getAdapterPosition(); // gets item position
|
||||||
|
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
|
||||||
|
listener.onInteraction(view, getItem(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongClick(View view) {
|
||||||
|
if (listener != null) {
|
||||||
|
int position = getAdapterPosition(); // gets item position
|
||||||
|
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
|
||||||
|
return listener.onLongInteraction(view, getItem(position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,6 +17,8 @@
|
|||||||
package com.m2049r.xmrwallet.layout;
|
package com.m2049r.xmrwallet.layout;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.Spanned;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -46,7 +48,6 @@ import java.util.TimeZone;
|
|||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
|
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
|
||||||
|
|
||||||
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||||
|
|
||||||
private final int outboundColour;
|
private final int outboundColour;
|
||||||
@@ -94,6 +95,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
|||||||
return (oldItem.direction == newItem.direction)
|
return (oldItem.direction == newItem.direction)
|
||||||
&& (oldItem.isPending == newItem.isPending)
|
&& (oldItem.isPending == newItem.isPending)
|
||||||
&& (oldItem.isFailed == newItem.isFailed)
|
&& (oldItem.isFailed == newItem.isFailed)
|
||||||
|
&& (oldItem.subaddressLabel.equals(newItem.subaddressLabel))
|
||||||
&& (Objects.equals(oldItem.notes, newItem.notes));
|
&& (Objects.equals(oldItem.notes, newItem.notes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,14 +213,25 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
|||||||
setTxColour(outboundColour);
|
setTxColour(outboundColour);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String tag = null;
|
||||||
|
String info = "";
|
||||||
|
if ((infoItem.addressIndex != 0) && (infoItem.direction == TransactionInfo.Direction.Direction_In))
|
||||||
|
tag = infoItem.getDisplayLabel();
|
||||||
if ((userNotes.note.isEmpty())) {
|
if ((userNotes.note.isEmpty())) {
|
||||||
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ?
|
if (!infoItem.paymentId.equals("0000000000000000")) {
|
||||||
(infoItem.subaddress != 0 ?
|
info = infoItem.paymentId;
|
||||||
(context.getString(R.string.tx_subaddress, infoItem.subaddress)) :
|
}
|
||||||
"") :
|
|
||||||
infoItem.paymentId);
|
|
||||||
} else {
|
} else {
|
||||||
this.tvPaymentId.setText(userNotes.note);
|
info = userNotes.note;
|
||||||
|
}
|
||||||
|
if (tag == null) {
|
||||||
|
tvPaymentId.setText(info);
|
||||||
|
} else {
|
||||||
|
Spanned label = Html.fromHtml(context.getString(R.string.tx_details_notes,
|
||||||
|
Integer.toHexString(ContextCompat.getColor(context, R.color.monerujoGreen) & 0xFFFFFF),
|
||||||
|
Integer.toHexString(ContextCompat.getColor(context, R.color.monerujoBackground) & 0xFFFFFF),
|
||||||
|
tag, info.isEmpty() ? "" : (" " + info)));
|
||||||
|
tvPaymentId.setText(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tvDateTime.setText(getDateTime(infoItem.timestamp));
|
this.tvDateTime.setText(getDateTime(infoItem.timestamp));
|
||||||
|
@@ -74,7 +74,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||||
return mOldList.get(oldItemPosition).name.equals(mNewList.get(newItemPosition).name);
|
return mOldList.get(oldItemPosition).getName().equals(mNewList.get(newItemPosition).getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -86,9 +86,9 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||||
View view = LayoutInflater.from(parent.getContext())
|
return new ViewHolder(
|
||||||
.inflate(R.layout.item_wallet, parent, false);
|
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_wallet, parent, false)
|
||||||
return new ViewHolder(view);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -122,7 +122,6 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
|||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||||
final TextView tvName;
|
final TextView tvName;
|
||||||
final TextView tvAddress;
|
|
||||||
final ImageButton ibOptions;
|
final ImageButton ibOptions;
|
||||||
WalletManager.WalletInfo infoItem;
|
WalletManager.WalletInfo infoItem;
|
||||||
boolean popupOpen = false;
|
boolean popupOpen = false;
|
||||||
@@ -130,7 +129,6 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
|||||||
ViewHolder(View itemView) {
|
ViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
tvName = itemView.findViewById(R.id.tvName);
|
tvName = itemView.findViewById(R.id.tvName);
|
||||||
tvAddress = itemView.findViewById(R.id.tvAddress);
|
|
||||||
ibOptions = itemView.findViewById(R.id.ibOptions);
|
ibOptions = itemView.findViewById(R.id.ibOptions);
|
||||||
ibOptions.setOnClickListener(view -> {
|
ibOptions.setOnClickListener(view -> {
|
||||||
if (popupOpen) return;
|
if (popupOpen) return;
|
||||||
@@ -160,8 +158,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
|
|||||||
|
|
||||||
void bind(int position) {
|
void bind(int position) {
|
||||||
infoItem = infoItems.get(position);
|
infoItem = infoItems.get(position);
|
||||||
tvName.setText(infoItem.name);
|
tvName.setText(infoItem.getName());
|
||||||
tvAddress.setText(infoItem.address.substring(0, 16) + "...");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@@ -71,18 +71,18 @@ public class TransactionHistory {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
List<TransactionInfo> t = refreshJ();
|
List<TransactionInfo> transactionInfos = refreshJ();
|
||||||
Timber.d("refreshed %d", t.size());
|
Timber.d("refreshed %d", transactionInfos.size());
|
||||||
for (Iterator<TransactionInfo> iterator = t.iterator(); iterator.hasNext(); ) {
|
for (Iterator<TransactionInfo> iterator = transactionInfos.iterator(); iterator.hasNext(); ) {
|
||||||
TransactionInfo info = iterator.next();
|
TransactionInfo info = iterator.next();
|
||||||
if (info.account != accountIndex) {
|
if (info.accountIndex != accountIndex) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
Timber.d("removed %s", info.hash);
|
Timber.d("removed %s", info.hash);
|
||||||
} else {
|
} else {
|
||||||
Timber.d("kept %s", info.hash);
|
Timber.d("kept %s", info.hash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transactions = t;
|
transactions = transactionInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
private native List<TransactionInfo> refreshJ();
|
private native List<TransactionInfo> refreshJ();
|
||||||
|
@@ -19,13 +19,17 @@ package com.m2049r.xmrwallet.model;
|
|||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
// this is not the TransactionInfo from the API as that is owned by the TransactionHistory
|
// this is not the TransactionInfo from the API as that is owned by the TransactionHistory
|
||||||
// this is a POJO for the TransactionInfoAdapter
|
// this is a POJO for the TransactionInfoAdapter
|
||||||
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
|
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
|
||||||
static final String TAG = "TransactionInfo";
|
@RequiredArgsConstructor
|
||||||
|
|
||||||
public enum Direction {
|
public enum Direction {
|
||||||
Direction_In(0),
|
Direction_In(0),
|
||||||
Direction_Out(1);
|
Direction_Out(1);
|
||||||
@@ -40,15 +44,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getValue() {
|
@Getter
|
||||||
return value;
|
private final int value;
|
||||||
}
|
|
||||||
|
|
||||||
private int value;
|
|
||||||
|
|
||||||
Direction(int value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Direction direction;
|
public Direction direction;
|
||||||
@@ -60,8 +57,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
public String hash;
|
public String hash;
|
||||||
public long timestamp;
|
public long timestamp;
|
||||||
public String paymentId;
|
public String paymentId;
|
||||||
public int account;
|
public int accountIndex;
|
||||||
public int subaddress;
|
public int addressIndex;
|
||||||
public long confirmations;
|
public long confirmations;
|
||||||
public String subaddressLabel;
|
public String subaddressLabel;
|
||||||
public List<Transfer> transfers;
|
public List<Transfer> transfers;
|
||||||
@@ -80,8 +77,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
String hash,
|
String hash,
|
||||||
long timestamp,
|
long timestamp,
|
||||||
String paymentId,
|
String paymentId,
|
||||||
int account,
|
int accountIndex,
|
||||||
int subaddress,
|
int addressIndex,
|
||||||
long confirmations,
|
long confirmations,
|
||||||
String subaddressLabel,
|
String subaddressLabel,
|
||||||
List<Transfer> transfers) {
|
List<Transfer> transfers) {
|
||||||
@@ -94,13 +91,20 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
this.hash = hash;
|
this.hash = hash;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.paymentId = paymentId;
|
this.paymentId = paymentId;
|
||||||
this.account = account;
|
this.accountIndex = accountIndex;
|
||||||
this.subaddress = subaddress;
|
this.addressIndex = addressIndex;
|
||||||
this.confirmations = confirmations;
|
this.confirmations = confirmations;
|
||||||
this.subaddressLabel = subaddressLabel;
|
this.subaddressLabel = subaddressLabel;
|
||||||
this.transfers = transfers;
|
this.transfers = transfers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDisplayLabel() {
|
||||||
|
if (subaddressLabel.isEmpty() || (Subaddress.DEFAULT_LABEL_FORMATTER.matcher(subaddressLabel).matches()))
|
||||||
|
return ("#" + addressIndex);
|
||||||
|
else
|
||||||
|
return subaddressLabel;
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return direction + "@" + blockheight + " " + amount;
|
return direction + "@" + blockheight + " " + amount;
|
||||||
}
|
}
|
||||||
@@ -116,8 +120,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
out.writeString(hash);
|
out.writeString(hash);
|
||||||
out.writeLong(timestamp);
|
out.writeLong(timestamp);
|
||||||
out.writeString(paymentId);
|
out.writeString(paymentId);
|
||||||
out.writeInt(account);
|
out.writeInt(accountIndex);
|
||||||
out.writeInt(subaddress);
|
out.writeInt(addressIndex);
|
||||||
out.writeLong(confirmations);
|
out.writeLong(confirmations);
|
||||||
out.writeString(subaddressLabel);
|
out.writeString(subaddressLabel);
|
||||||
out.writeList(transfers);
|
out.writeList(transfers);
|
||||||
@@ -146,8 +150,8 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
|||||||
hash = in.readString();
|
hash = in.readString();
|
||||||
timestamp = in.readLong();
|
timestamp = in.readLong();
|
||||||
paymentId = in.readString();
|
paymentId = in.readString();
|
||||||
account = in.readInt();
|
accountIndex = in.readInt();
|
||||||
subaddress = in.readInt();
|
addressIndex = in.readInt();
|
||||||
confirmations = in.readLong();
|
confirmations = in.readLong();
|
||||||
subaddressLabel = in.readString();
|
subaddressLabel = in.readString();
|
||||||
transfers = in.readArrayList(Transfer.class.getClassLoader());
|
transfers = in.readArrayList(Transfer.class.getClassLoader());
|
||||||
|
@@ -19,6 +19,7 @@ package com.m2049r.xmrwallet.model;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
import com.m2049r.xmrwallet.data.TxData;
|
import com.m2049r.xmrwallet.data.TxData;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -26,6 +27,8 @@ import java.text.SimpleDateFormat;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class Wallet {
|
public class Wallet {
|
||||||
@@ -104,10 +107,14 @@ public class Wallet {
|
|||||||
this.accountIndex = accountIndex;
|
this.accountIndex = accountIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Getter
|
||||||
public enum Device {
|
public enum Device {
|
||||||
Device_Undefined,
|
Device_Undefined(0, 0),
|
||||||
Device_Software,
|
Device_Software(50, 200),
|
||||||
Device_Ledger
|
Device_Ledger(5, 20);
|
||||||
|
private final int accountLookahead;
|
||||||
|
private final int subaddressLookahead;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StatusEnum {
|
public enum StatusEnum {
|
||||||
@@ -160,6 +167,24 @@ public class Wallet {
|
|||||||
|
|
||||||
private native String getAddressJ(int accountIndex, int addressIndex);
|
private native String getAddressJ(int accountIndex, int addressIndex);
|
||||||
|
|
||||||
|
public Subaddress getSubaddressObject(int accountIndex, int subAddressIndex) {
|
||||||
|
return new Subaddress(accountIndex, subAddressIndex,
|
||||||
|
getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Subaddress getSubaddressObject(int subAddressIndex) {
|
||||||
|
Subaddress subaddress = getSubaddressObject(accountIndex, subAddressIndex);
|
||||||
|
long amount = 0;
|
||||||
|
for (TransactionInfo info : getHistory().getAll()) {
|
||||||
|
if ((info.addressIndex == subAddressIndex)
|
||||||
|
&& (info.direction == TransactionInfo.Direction.Direction_In)) {
|
||||||
|
amount += info.amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subaddress.setAmount(amount);
|
||||||
|
return subaddress;
|
||||||
|
}
|
||||||
|
|
||||||
public native String getPath();
|
public native String getPath();
|
||||||
|
|
||||||
public NetworkType getNetworkType() {
|
public NetworkType getNetworkType() {
|
||||||
@@ -428,6 +453,11 @@ public class Wallet {
|
|||||||
setSubaddressLabel(accountIndex, 0, label);
|
setSubaddressLabel(accountIndex, 0, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSubaddressLabel(int addressIndex, String label) {
|
||||||
|
setSubaddressLabel(accountIndex, addressIndex, label);
|
||||||
|
getHistory().refreshWithNotes(this);
|
||||||
|
}
|
||||||
|
|
||||||
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
|
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
|
||||||
|
|
||||||
public native int getNumAccounts();
|
public native int getNumAccounts();
|
||||||
|
@@ -21,15 +21,13 @@ import com.m2049r.xmrwallet.data.Node;
|
|||||||
import com.m2049r.xmrwallet.ledger.Ledger;
|
import com.m2049r.xmrwallet.ledger.Ledger;
|
||||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class WalletManager {
|
public class WalletManager {
|
||||||
@@ -103,7 +101,8 @@ public class WalletManager {
|
|||||||
wallet.setRestoreHeight(restoreHeight);
|
wallet.setRestoreHeight(restoreHeight);
|
||||||
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight());
|
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight());
|
||||||
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
|
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
|
||||||
}
|
} else
|
||||||
|
Timber.e(wallet.getStatus().toString());
|
||||||
return wallet;
|
return wallet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,46 +209,22 @@ public class WalletManager {
|
|||||||
//public native List<String> findWallets(String path); // this does not work - some error in boost
|
//public native List<String> findWallets(String path); // this does not work - some error in boost
|
||||||
|
|
||||||
public class WalletInfo implements Comparable<WalletInfo> {
|
public class WalletInfo implements Comparable<WalletInfo> {
|
||||||
public File path;
|
@Getter
|
||||||
public String name;
|
final private File path;
|
||||||
public String address;
|
@Getter
|
||||||
|
final private String name;
|
||||||
|
|
||||||
|
public WalletInfo(File wallet) {
|
||||||
|
path = wallet.getParentFile();
|
||||||
|
name = wallet.getName();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(WalletInfo another) {
|
public int compareTo(WalletInfo another) {
|
||||||
int n = name.toLowerCase().compareTo(another.name.toLowerCase());
|
return name.toLowerCase().compareTo(another.name.toLowerCase());
|
||||||
if (n != 0) {
|
|
||||||
return n;
|
|
||||||
} else { // wallet names are the same
|
|
||||||
return address.compareTo(another.address);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public WalletInfo getWalletInfo(File wallet) {
|
|
||||||
WalletInfo info = new WalletInfo();
|
|
||||||
info.path = wallet.getParentFile();
|
|
||||||
info.name = wallet.getName();
|
|
||||||
File addressFile = new File(info.path, info.name + ".address.txt");
|
|
||||||
//Timber.d(addressFile.getAbsolutePath());
|
|
||||||
info.address = "??????";
|
|
||||||
BufferedReader addressReader = null;
|
|
||||||
try {
|
|
||||||
addressReader = new BufferedReader(new FileReader(addressFile));
|
|
||||||
info.address = addressReader.readLine();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
Timber.d(ex.getLocalizedMessage());
|
|
||||||
} finally {
|
|
||||||
if (addressReader != null) {
|
|
||||||
try {
|
|
||||||
addressReader.close();
|
|
||||||
} catch (IOException ex) {
|
|
||||||
// that's just too bad
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<WalletInfo> findWallets(File path) {
|
public List<WalletInfo> findWallets(File path) {
|
||||||
List<WalletInfo> wallets = new ArrayList<>();
|
List<WalletInfo> wallets = new ArrayList<>();
|
||||||
Timber.d("Scanning: %s", path.getAbsolutePath());
|
Timber.d("Scanning: %s", path.getAbsolutePath());
|
||||||
@@ -261,7 +236,7 @@ public class WalletManager {
|
|||||||
for (int i = 0; i < found.length; i++) {
|
for (int i = 0; i < found.length; i++) {
|
||||||
String filename = found[i].getName();
|
String filename = found[i].getName();
|
||||||
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
|
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
|
||||||
wallets.add(getWalletInfo(f));
|
wallets.add(new WalletInfo(f));
|
||||||
}
|
}
|
||||||
return wallets;
|
return wallets;
|
||||||
}
|
}
|
||||||
|
@@ -167,7 +167,7 @@ public class ExchangeApiImpl implements ExchangeApi {
|
|||||||
private Calendar fetchDate = null;
|
private Calendar fetchDate = null;
|
||||||
|
|
||||||
synchronized private ExchangeRate getRate(String currency) throws ExchangeException {
|
synchronized private ExchangeRate getRate(String currency) throws ExchangeException {
|
||||||
Timber.e("Getting %s", currency);
|
Timber.d("Getting %s", currency);
|
||||||
final Double rate = fxEntries.get(currency);
|
final Double rate = fxEntries.get(currency);
|
||||||
if (rate == null) throw new ExchangeException(404, "Currency not supported: " + currency);
|
if (rate == null) throw new ExchangeException(404, "Currency not supported: " + currency);
|
||||||
return new ExchangeRateImpl(currency, rate, fxDate.getTime());
|
return new ExchangeRateImpl(currency, rate, fxDate.getTime());
|
||||||
|
@@ -34,7 +34,6 @@ import android.hardware.fingerprint.FingerprintManager;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.CancellationSignal;
|
import android.os.CancellationSignal;
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.system.ErrnoException;
|
import android.system.ErrnoException;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
@@ -58,6 +57,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
|||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
import com.m2049r.xmrwallet.BuildConfig;
|
import com.m2049r.xmrwallet.BuildConfig;
|
||||||
import com.m2049r.xmrwallet.R;
|
import com.m2049r.xmrwallet.R;
|
||||||
|
import com.m2049r.xmrwallet.data.Crypto;
|
||||||
import com.m2049r.xmrwallet.model.WalletManager;
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -77,16 +77,17 @@ import javax.net.ssl.HttpsURLConnection;
|
|||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class Helper {
|
public class Helper {
|
||||||
static private final String FLAVOR_SUFFIX =
|
|
||||||
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
|
|
||||||
+ (BuildConfig.DEBUG ? "-debug" : "");
|
|
||||||
|
|
||||||
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
|
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
|
||||||
|
|
||||||
static public final String BASE_CRYPTO = "XMR";
|
static public final String BASE_CRYPTO = Crypto.XMR.getSymbol();
|
||||||
|
static public final int XMR_DECIMALS = 12;
|
||||||
|
static public final long ONE_XMR = Math.round(Math.pow(10, Helper.XMR_DECIMALS));
|
||||||
|
|
||||||
static private final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
|
static public final boolean SHOW_EXCHANGERATES = true;
|
||||||
static private final String HOME_DIR = "monero" + FLAVOR_SUFFIX;
|
static public final boolean ALLOW_SHIFT = true;
|
||||||
|
|
||||||
|
static private final String WALLET_DIR = "wallets";
|
||||||
|
static private final String MONERO_DIR = "monero";
|
||||||
|
|
||||||
static public int DISPLAY_DIGITS_INFO = 5;
|
static public int DISPLAY_DIGITS_INFO = 5;
|
||||||
|
|
||||||
@@ -95,12 +96,7 @@ public class Helper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public File getStorage(Context context, String folderName) {
|
static public File getStorage(Context context, String folderName) {
|
||||||
if (!isExternalStorageWritable()) {
|
File dir = new File(context.getFilesDir(), folderName);
|
||||||
String msg = context.getString(R.string.message_strorage_not_writable);
|
|
||||||
Timber.e(msg);
|
|
||||||
throw new IllegalStateException(msg);
|
|
||||||
}
|
|
||||||
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
|
||||||
@@ -113,24 +109,6 @@ public class Helper {
|
|||||||
return dir;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
|
|
||||||
|
|
||||||
static public boolean getWritePermission(Activity context) {
|
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
|
||||||
if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
|
||||||
== PackageManager.PERMISSION_DENIED) {
|
|
||||||
Timber.w("Permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
|
|
||||||
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
|
||||||
context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
|
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
|
||||||
|
|
||||||
static public boolean getCameraPermission(Activity context) {
|
static public boolean getCameraPermission(Activity context) {
|
||||||
@@ -156,12 +134,6 @@ public class Helper {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Checks if external storage is available for read and write */
|
|
||||||
private static boolean isExternalStorageWritable() {
|
|
||||||
String state = Environment.getExternalStorageState();
|
|
||||||
return Environment.MEDIA_MOUNTED.equals(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public void showKeyboard(Activity act) {
|
static public void showKeyboard(Activity act) {
|
||||||
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
|
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
final View focus = act.getCurrentFocus();
|
final View focus = act.getCurrentFocus();
|
||||||
@@ -189,11 +161,11 @@ public class Helper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static public BigDecimal getDecimalAmount(long amount) {
|
static public BigDecimal getDecimalAmount(long amount) {
|
||||||
return new BigDecimal(amount).scaleByPowerOfTen(-12);
|
return new BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getDisplayAmount(long amount) {
|
static public String getDisplayAmount(long amount) {
|
||||||
return getDisplayAmount(amount, 12);
|
return getDisplayAmount(amount, XMR_DECIMALS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getDisplayAmount(long amount, int maxDecimals) {
|
static public String getDisplayAmount(long amount, int maxDecimals) {
|
||||||
@@ -225,7 +197,7 @@ public class Helper {
|
|||||||
static public String getDisplayAmount(double amount) {
|
static public String getDisplayAmount(double amount) {
|
||||||
// a Java bug does not strip zeros properly if the value is 0
|
// a Java bug does not strip zeros properly if the value is 0
|
||||||
BigDecimal d = new BigDecimal(amount)
|
BigDecimal d = new BigDecimal(amount)
|
||||||
.setScale(12, BigDecimal.ROUND_HALF_UP)
|
.setScale(XMR_DECIMALS, BigDecimal.ROUND_HALF_UP)
|
||||||
.stripTrailingZeros();
|
.stripTrailingZeros();
|
||||||
if (d.scale() < 1)
|
if (d.scale() < 1)
|
||||||
d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY);
|
d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY);
|
||||||
@@ -339,7 +311,7 @@ public class Helper {
|
|||||||
|
|
||||||
static public void setMoneroHome(Context context) {
|
static public void setMoneroHome(Context context) {
|
||||||
try {
|
try {
|
||||||
String home = getStorage(context, HOME_DIR).getAbsolutePath();
|
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
||||||
Os.setenv("HOME", home, true);
|
Os.setenv("HOME", home, true);
|
||||||
} catch (ErrnoException ex) {
|
} catch (ErrnoException ex) {
|
||||||
throw new IllegalStateException(ex);
|
throw new IllegalStateException(ex);
|
||||||
@@ -355,7 +327,7 @@ public class Helper {
|
|||||||
|
|
||||||
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
|
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
|
||||||
static public void initLogger(Context context, int level) {
|
static public void initLogger(Context context, int level) {
|
||||||
String home = getStorage(context, HOME_DIR).getAbsolutePath();
|
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
||||||
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
|
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
|
||||||
if (level >= WalletManager.LOGLEVEL_SILENT)
|
if (level >= WalletManager.LOGLEVEL_SILENT)
|
||||||
WalletManager.setLogLevel(level);
|
WalletManager.setLogLevel(level);
|
||||||
|
@@ -24,9 +24,10 @@ import android.os.Build;
|
|||||||
import android.security.KeyPairGeneratorSpec;
|
import android.security.KeyPairGeneratorSpec;
|
||||||
import android.security.keystore.KeyGenParameterSpec;
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
import android.security.keystore.KeyProperties;
|
import android.security.keystore.KeyProperties;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -167,6 +168,11 @@ public class KeyStoreHelper {
|
|||||||
.remove(wallet).apply();
|
.remove(wallet).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void copyWalletUserPass(Context context, String srcWallet, String dstWallet) throws BrokenPasswordStoreException {
|
||||||
|
final String pass = loadWalletUserPass(context, srcWallet);
|
||||||
|
saveWalletUserPass(context, dstWallet, pass);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a public and private key and stores it using the Android Key
|
* Creates a public and private key and stores it using the Android Key
|
||||||
* Store, so that only this application will be able to access the keys.
|
* Store, so that only this application will be able to access the keys.
|
||||||
|
@@ -0,0 +1,170 @@
|
|||||||
|
package com.m2049r.xmrwallet.util;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Environment;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.BuildConfig;
|
||||||
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LegacyStorageHelper {
|
||||||
|
final private File srcDir;
|
||||||
|
final private File dstDir;
|
||||||
|
|
||||||
|
static public void migrateWallets(Context context) {
|
||||||
|
try {
|
||||||
|
if (isStorageMigrated(context)) return;
|
||||||
|
if (!hasReadPermission(context)) {
|
||||||
|
// can't migrate - don't remember this, as the user may turn on permissions later
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final File oldRoot = getWalletRoot();
|
||||||
|
if (!oldRoot.exists()) {
|
||||||
|
// nothing to migrate, so don't try again
|
||||||
|
setStorageMigrated(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final File newRoot = Helper.getWalletRoot(context);
|
||||||
|
(new LegacyStorageHelper(oldRoot, newRoot)).migrate();
|
||||||
|
setStorageMigrated(context); // done it once - don't try again
|
||||||
|
} catch (IllegalStateException ex) {
|
||||||
|
Timber.d(ex);
|
||||||
|
// nothing we can do here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void migrate() {
|
||||||
|
String addressPrefix = WalletManager.getInstance().addressPrefix();
|
||||||
|
File[] wallets = srcDir.listFiles((dir, filename) -> filename.endsWith(".keys"));
|
||||||
|
if (wallets == null) return;
|
||||||
|
for (File wallet : wallets) {
|
||||||
|
final String walletName = wallet.getName().substring(0, wallet.getName().length() - ".keys".length());
|
||||||
|
if (addressPrefix.indexOf(getAddress(walletName).charAt(0)) < 0) {
|
||||||
|
Timber.d("skipping %s", walletName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
copy(walletName);
|
||||||
|
} catch (IOException ex) { // something failed - try to clean up
|
||||||
|
deleteDst(walletName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return "@" by default so we don't need to deal with null stuff
|
||||||
|
private String getAddress(String walletName) {
|
||||||
|
File addressFile = new File(srcDir, walletName + ".address.txt");
|
||||||
|
if (!addressFile.exists()) return "@";
|
||||||
|
try (BufferedReader addressReader = new BufferedReader(new FileReader(addressFile))) {
|
||||||
|
return addressReader.readLine();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Timber.d(ex.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
return "@";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copy(String walletName) throws IOException {
|
||||||
|
final String dstName = getUniqueName(dstDir, walletName);
|
||||||
|
copyFile(new File(srcDir, walletName), new File(dstDir, dstName));
|
||||||
|
copyFile(new File(srcDir, walletName + ".keys"), new File(dstDir, dstName + ".keys"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteDst(String walletName) {
|
||||||
|
// do our best, but if it fails, it fails
|
||||||
|
(new File(dstDir, walletName)).delete();
|
||||||
|
(new File(dstDir, walletName + ".keys")).delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyFile(File src, File dst) throws IOException {
|
||||||
|
if (!src.exists()) return;
|
||||||
|
Timber.d("%s => %s", src.getAbsolutePath(), dst.getAbsolutePath());
|
||||||
|
try (FileChannel inChannel = new FileInputStream(src).getChannel();
|
||||||
|
FileChannel outChannel = new FileOutputStream(dst).getChannel()) {
|
||||||
|
inChannel.transferTo(0, inChannel.size(), outChannel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isExternalStorageWritable() {
|
||||||
|
String state = Environment.getExternalStorageState();
|
||||||
|
return Environment.MEDIA_MOUNTED.equals(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File getWalletRoot() {
|
||||||
|
if (!isExternalStorageWritable())
|
||||||
|
throw new IllegalStateException();
|
||||||
|
|
||||||
|
// wallet folder for legacy (pre-Q) installations
|
||||||
|
final String FLAVOR_SUFFIX =
|
||||||
|
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
|
||||||
|
+ (BuildConfig.DEBUG ? "-debug" : "");
|
||||||
|
final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
|
||||||
|
|
||||||
|
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
|
||||||
|
if (!dir.exists() || !dir.isDirectory())
|
||||||
|
throw new IllegalStateException();
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasReadPermission(Context context) {
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||||
|
return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
|
||||||
|
|
||||||
|
private static String getUniqueName(File root, String name) {
|
||||||
|
if (!(new File(root, name + ".keys")).exists()) // <name> does not exist => it's ok to use
|
||||||
|
return name;
|
||||||
|
|
||||||
|
File[] wallets = root.listFiles(
|
||||||
|
(dir, filename) -> {
|
||||||
|
Matcher m = WALLET_PATTERN.matcher(filename);
|
||||||
|
if (m.find())
|
||||||
|
return m.group(1).equals(name);
|
||||||
|
else return false;
|
||||||
|
});
|
||||||
|
if (wallets.length == 0) return name + " (1)";
|
||||||
|
int maxIndex = 0;
|
||||||
|
for (File wallet : wallets) {
|
||||||
|
try {
|
||||||
|
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
|
||||||
|
if (!m.find())
|
||||||
|
throw new IllegalStateException("this must match as it did before");
|
||||||
|
final int index = Integer.parseInt(m.group(2));
|
||||||
|
if (index > maxIndex) maxIndex = index;
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
// this cannot happen & we can ignore it if it does
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name + " (" + (maxIndex + 1) + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String MIGRATED_KEY = "migrated_legacy_storage";
|
||||||
|
|
||||||
|
public static boolean isStorageMigrated(Context context) {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setStorageMigrated(Context context) {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user