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

Compare commits

...

39 Commits

Author SHA1 Message Date
m2049r
5b38cc438c Merge pull request #27 from m2049r/docs
update docs and version
2017-08-30 22:24:49 +02:00
m2049r
1eac363102 update docs and version 2017-08-30 22:24:24 +02:00
m2049r
e51425bc2e Merge pull request #26 from m2049r/feature_qr
QR Scanning incl. payment id
2017-08-30 22:02:34 +02:00
m2049r
f5535dbefa QR Scanning incl. payment id 2017-08-30 21:36:07 +02:00
m2049r
2d42a0287d Merge pull request #25 from m2049r/docs
updated version number + docs
2017-08-29 23:41:38 +02:00
m2049r
2ebb9d0650 updated version number + docs 2017-08-29 23:40:46 +02:00
m2049r
30792c53c0 Merge pull request #24 from m2049r/feature_spend_mainnet
mainnet spending
2017-08-29 22:37:23 +02:00
m2049r
3846a0c391 mainnet spending 2017-08-29 22:22:07 +02:00
m2049r
c66ebd654a Merge pull request #23 from m2049r/cleanup
Lots of GUI tweaks
2017-08-29 22:07:17 +02:00
m2049r
186ed5cd39 second spend confirmation for mainnet 2017-08-29 22:05:51 +02:00
m2049r
e39cd1c988 lots of GUI tweaks 2017-08-29 22:05:45 +02:00
m2049r
39d9c4d0c3 Merge pull request #22 from m2049r/feature_colours
balance colour + tx ordering
2017-08-29 08:44:36 +02:00
m2049r
e4562f07a4 balance colour + tx ordering 2017-08-29 08:43:56 +02:00
m2049r
b3ea4540a1 Merge pull request #21 from m2049r/feature_toolbar
Toolbars + action & context menu
2017-08-28 23:51:36 +02:00
m2049r
ea7c7d2fdb net colour in toolbar 2017-08-28 23:46:18 +02:00
m2049r
8ff8be6f60 list context menu + tweaks 2017-08-28 23:14:53 +02:00
m2049r
b5ded700fe toolbar info action for wallet activity 2017-08-28 22:10:17 +02:00
m2049r
a5527d4efd use default daemon ports if none supplied 2017-08-28 19:38:13 +02:00
m2049r
a007810824 Merge pull request #20 from m2049r/feature_list_addfab
nicer add icon
2017-08-27 22:58:36 +02:00
m2049r
a634cf18b1 nicer fab icon 2017-08-27 22:57:35 +02:00
m2049r
0b2fa08df8 Merge pull request #19 from m2049r/feature_list_addfab
Create wallet by action button
2017-08-27 09:14:52 +02:00
m2049r
191aa6ac22 action button for create wallet + setting activity title in LoginActivity 2017-08-27 09:12:53 +02:00
m2049r
a82a90575d deamon prompt show syntax 2017-08-26 00:34:17 +02:00
m2049r
2e04046f50 Merge pull request #18 from m2049r/feature_layout
simple layout
2017-08-26 00:29:41 +02:00
m2049r
c11d577c5f simple layout
minimum number of decimal places
progress at top
2017-08-26 00:27:27 +02:00
m2049r
74b2dd209c Merge pull request #17 from m2049r/feature_daemon_userpassword
Username/Password for daemon
2017-08-24 00:06:18 +02:00
m2049r
cb1d541474 Username/Password for daemon 2017-08-24 00:03:23 +02:00
m2049r
807d217aac Merge pull request #16 from m2049r/feature_txrecipient
show destinations in tx view
2017-08-22 13:10:14 +02:00
m2049r
0421bcfac3 show destination in tx view 2017-08-22 13:02:31 +02:00
m2049r
b6c4e06fda Merge pull request #15 from m2049r/feature_spend_redo
enable dispose of prepared transaction
2017-08-22 11:10:19 +02:00
m2049r
3ee8343074 enable dispose of prepared transaction 2017-08-22 11:05:43 +02:00
m2049r
110621824a Merge pull request #14 from m2049r/hotfix_priority
passing of transaction priority fixed
2017-08-22 10:56:07 +02:00
m2049r
7194580fbe passing of transaction priority fixed 2017-08-22 10:54:16 +02:00
m2049r
48dcd37740 Update build.gradle 2017-08-22 00:19:53 +02:00
m2049r
8e619756b9 Merge pull request #13 from m2049r/feature_service_notification
start WalletService as foreground service
2017-08-21 17:33:08 +02:00
m2049r
6c6b47db7d Merge pull request #12 from m2049r/bugfix
don't show Send Button on mainnet (instead of crashing when clicked)
2017-08-21 17:31:45 +02:00
m2049r
a126844272 start WalletService as foreground service preventing it from being killed by the system 2017-08-21 15:58:22 +02:00
m2049r
9b3086ff2e don't show Send Button on mainnet (instead of crashing when clicked) 2017-08-21 10:14:19 +02:00
m2049r
89d1842d59 Update README.md 2017-08-21 00:22:57 +02:00
43 changed files with 1311 additions and 646 deletions

10
.idea/libraries/core_1_9_8.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="core-1.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/db7eb580bfa6467818ef12c5f1fa42273b50aa3a/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/db7eb580bfa6467818ef12c5f1fa42273b50aa3a/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

11
.idea/libraries/core_3_3_0.xml generated Normal file
View File

@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="core-3.3.0">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.zxing/core/3.3.0/73c49077166faa4c3c0059c5f583d1d7bd1475fe/core-3.3.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.google.zxing/core/3.3.0/39d966e052e28fc7d83793b02e0af97ccf955745/core-3.3.0-sources.jar!/" />
</SOURCES>
</library>
</component>

10
.idea/libraries/zxing_1_9_8.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="zxing-1.9.8">
<CLASSES>
<root url="jar://$USER_HOME$/.android/build-cache/8d8f2e0e10c7af73321454f6fa481e8e5438e654/output/jars/classes.jar!/" />
<root url="file://$USER_HOME$/.android/build-cache/8d8f2e0e10c7af73321454f6fa481e8e5438e654/output/res" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -8,12 +8,11 @@ Another Android Monero Wallet
- see the [FAQ](doc/FAQ.md)
### Disclaimer
You may loose all your Moneroj if you use this App.
You may loose all your Moneroj if you use this App. Be cautious when spending on mainnet.
### Random Notes
- Based off monero v0.10.3.1 with pull requests #2238, #2239 and #2289 applied => so can be used in mainnet!
- currently only android32
- ~~currently only use is checking incoming/outgoing transactions~~
- currently only android32 (runs on 64-bit as well)
- works in testnet & mainnet
- takes forever to sync due to 32-bit architecture
- use your own daemon - it's easy
@@ -23,15 +22,12 @@ You may loose all your Moneroj if you use this App.
### TODO
- wallet backup functions
- adjust layout so we can use bigger font sizes (maybe show only 5 decimal places instead of 12 in main view)
- review visibility of methods/classes
- more sensible error dialogs ~~(e.g. when no write permissions granted) instead of just crashing on purpose~~
- more sensible error dialogs
- check licenses of included libraries; License Dialog
- ~~make it pretty~~ (decided to go with "form follows function")
- ~~spend monero - not so difficult with wallet api~~
### Issues
- Pending incoming transactions disappear after reload
- Pending incoming transactions disappear after reload (and appear after being mined)
### HOW TO BUILD
No need to build. Binaries are included:
@@ -43,3 +39,7 @@ No need to build. Binaries are included:
If you want to build them yourself (recommended) check out [the instructions](doc/BUILDING-external-libs.md)
Then, fire up Android Studio and build the APK.
### Donations
- Address: 4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk
- Viewkey: b1aff2a12191723da0afbe75516f94dd8b068215f6e847d8da57aca5f1f98e0c

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
versionCode 4
versionName "0.4.0"
versionCode 8
versionName "0.5.1"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -46,5 +46,6 @@ dependencies {
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
compile 'com.android.support:cardview-v7:25.3.1'
compile 'me.dm7.barcodescanner:zxing:1.9.8'
testCompile 'junit:junit:4.12'
}

View File

@@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<application
android:allowBackup="true"
@@ -19,6 +20,7 @@
android:name=".WalletActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/wallet_activity_name"
android:launchMode="singleTask"
android:screenOrientation="portrait" />
<activity

View File

@@ -592,12 +592,16 @@ Java_com_m2049r_xmrwallet_model_Wallet_getFilename(JNIEnv *env, jobject instance
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_initJ(JNIEnv *env, jobject instance,
jstring daemon_address,
jlong upper_transaction_size_limit) {
// const std::string &daemon_username = "", const std::string &daemon_password = "") = 0;
jlong upper_transaction_size_limit,
jstring daemon_username, jstring daemon_password) {
const char *_daemon_address = env->GetStringUTFChars(daemon_address, JNI_FALSE);
const char *_daemon_username = env->GetStringUTFChars(daemon_username, JNI_FALSE);
const char *_daemon_password = env->GetStringUTFChars(daemon_password, JNI_FALSE);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool status = wallet->init(_daemon_address, upper_transaction_size_limit);
bool status = wallet->init(_daemon_address, upper_transaction_size_limit, _daemon_username, _daemon_password);
env->ReleaseStringUTFChars(daemon_address, _daemon_address);
env->ReleaseStringUTFChars(daemon_username, _daemon_username);
env->ReleaseStringUTFChars(daemon_password, _daemon_password);
return status;
}

View File

@@ -22,6 +22,7 @@ import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -344,6 +345,13 @@ public class GenerateFragment extends Fragment {
bGenerate.setVisibility(View.VISIBLE);
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onPause()");
activityCallback.setTitle(getString(R.string.generate_title));
}
GenerateFragment.Listener activityCallback;
public interface Listener {
@@ -354,6 +362,9 @@ public class GenerateFragment extends Fragment {
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
File getStorageRoot();
void setTitle(String title);
}
@Override

View File

@@ -65,6 +65,7 @@ public class GenerateReviewFragment extends Fragment {
boolean testnet = WalletManager.getInstance().isTestNet();
tvWalletMnemonic.setTextIsSelectable(testnet);
tvWalletSpendKey.setTextIsSelectable(testnet);
bAccept.setOnClickListener(new View.OnClickListener() {
@Override

View File

@@ -55,6 +55,8 @@ public class LoginActivity extends AppCompatActivity
static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms
Toolbar toolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -63,8 +65,7 @@ public class LoginActivity extends AppCompatActivity
return;
}
Toolbar toolbar = (Toolbar) findViewById(R.id.tbLogin);
toolbar.setTitle(R.string.login_activity_name);
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (Helper.getWritePermission(this)) {
@@ -77,21 +78,17 @@ public class LoginActivity extends AppCompatActivity
@Override
public void onWalletSelected(final String walletName) {
Log.d(TAG, "selected wallet is ." + walletName + ".");
if (walletName.equals(':' + getString(R.string.generate_title))) {
startGenerateFragment();
} else {
// now it's getting real, check if wallet exists
String walletPath = Helper.getWalletPath(this, walletName);
if (WalletManager.getInstance().walletExists(walletPath)) {
promptPassword(walletName, new PasswordAction() {
@Override
public void action(String walletName, String password) {
startWallet(walletName, password);
}
});
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
}
// now it's getting real, check if wallet exists
String walletPath = Helper.getWalletPath(this, walletName);
if (WalletManager.getInstance().walletExists(walletPath)) {
promptPassword(walletName, new PasswordAction() {
@Override
public void action(String walletName, String password) {
startWallet(walletName, password);
}
});
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
}
}
@@ -111,6 +108,11 @@ public class LoginActivity extends AppCompatActivity
}
}
@Override
public void onAddWallet() {
startGenerateFragment();
}
AlertDialog passwordDialog = null; // for preventing multiple clicks in wallet list
void promptPassword(final String wallet, final PasswordAction action) {
@@ -203,9 +205,22 @@ public class LoginActivity extends AppCompatActivity
@Override
public void setTitle(String title) {
super.setTitle(title);
toolbar.setTitle(title);
}
@Override
public void setSubtitle(String subtitle) {
toolbar.setSubtitle(subtitle);
}
@Override
public void setTestNet(boolean testnet) {
if (testnet) {
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
} else {
toolbar.setBackgroundResource(R.color.moneroOrange);
}
}
////////////////////////////////////////
////////////////////////////////////////

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) 2017 dm77, m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import me.dm7.barcodescanner.zxing.ZXingScannerView;
public class ScannerFragment extends Fragment implements ZXingScannerView.ResultHandler {
static final String TAG = "ScannerFragment";
Listener activityCallback;
public interface Listener {
void onAddressScanned(String address, String paymentId);
boolean isPaymentIdValid(String paymentId);
}
private ZXingScannerView mScannerView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
mScannerView = new ZXingScannerView(getActivity());
return mScannerView;
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume");
mScannerView.setResultHandler(this);
mScannerView.startCamera();
}
static final String URI_PREFIX = "monero:";
static final String PAYMENTID_STRING = "?tx_payment_id=";
@Override
public void handleResult(Result rawResult) {
Log.d(TAG, rawResult.getBarcodeFormat().toString() + "/" + rawResult.getText());
String text = rawResult.getText();
if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE) &&
(text.startsWith(URI_PREFIX))) {
String address = null;
String paymentId = null;
String s = text.substring(URI_PREFIX.length());
if (s.length() == 95) {
address = s;
} else {
int i = s.indexOf(PAYMENTID_STRING);
if ((i == 95) && (s.length() == (95 + PAYMENTID_STRING.length() + 16))) {
address = s.substring(0, 95);
paymentId = s.substring(95 + PAYMENTID_STRING.length());
if (!activityCallback.isPaymentIdValid(paymentId)) {
address = null;
}
}
}
if (Helper.isAddressOk(address, WalletManager.getInstance().isTestNet())) {
activityCallback.onAddressScanned(address, paymentId);
return;
} else {
Toast.makeText(getActivity(), getString(R.string.send_qr_address_invalid), Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getActivity(), getString(R.string.send_qr_invalid), Toast.LENGTH_SHORT).show();
}
// Note from dm77:
// * Wait 2 seconds to resume the preview.
// * On older devices continuously stopping and resuming camera preview can result in freezing the app.
// * I don't know why this is the case but I don't have the time to figure out.
Handler handler = new Handler();
handler.postDelayed(new
Runnable() {
@Override
public void run() {
mScannerView.resumeCameraPreview(ScannerFragment.this);
}
}, 2000);
}
@Override
public void onPause() {
Log.d(TAG, "onPause");
mScannerView.stopCamera();
super.onPause();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
Log.d(TAG, "attaching scan");
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
} else {
throw new ClassCastException(context.toString()
+ " must implement Listener");
}
}
}

View File

@@ -17,11 +17,10 @@
package com.m2049r.xmrwallet;
import android.app.Fragment;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.text.InputType;
@@ -40,23 +39,24 @@ import android.widget.Spinner;
import android.widget.TextView;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.BarcodeData;
import com.m2049r.xmrwallet.util.TxData;
public class SendFragment extends Fragment {
static final String TAG = "GenerateFragment";
static final String TAG = "SendFragment";
EditText etAddress;
EditText etPaymentId;
EditText etAmount;
Button bAddress;
Button bSweep;
Spinner sMixin;
Spinner sPriority;
Button bPrepareSend;
Button bDispose;
Button bPaymentId;
LinearLayout llConfirmSend;
TextView tvTxAmount;
@@ -64,6 +64,7 @@ public class SendFragment extends Fragment {
TextView tvTxDust;
EditText etNotes;
Button bSend;
Button bReallySend;
ProgressBar pbProgress;
final static int Mixins[] = {4, 6, 8, 10, 13}; // must match the layout XML
@@ -83,9 +84,11 @@ public class SendFragment extends Fragment {
etAddress = (EditText) view.findViewById(R.id.etAddress);
etPaymentId = (EditText) view.findViewById(R.id.etPaymentId);
etAmount = (EditText) view.findViewById(R.id.etAmount);
bAddress = (Button) view.findViewById(R.id.bAddress);
bSweep = (Button) view.findViewById(R.id.bSweep);
bPrepareSend = (Button) view.findViewById(R.id.bPrepareSend);
bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
bDispose = (Button) view.findViewById(R.id.bDispose);
llConfirmSend = (LinearLayout) view.findViewById(R.id.llConfirmSend);
tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount);
@@ -93,6 +96,7 @@ public class SendFragment extends Fragment {
tvTxDust = (TextView) view.findViewById(R.id.tvTxDust);
etNotes = (EditText) view.findViewById(R.id.etNotes);
bSend = (Button) view.findViewById(R.id.bSend);
bReallySend = (Button) view.findViewById(R.id.bReallySend);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
@@ -100,10 +104,6 @@ public class SendFragment extends Fragment {
etPaymentId.setRawInputType(InputType.TYPE_CLASS_TEXT);
etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
// etAddress.setText("9tDC52GsMjTNt4dpnRCwAF7ekVBkbkgkXGaMKTcSTpBhGpqkPX56jCNRydLq9oGjbbAQBsZhLfgmTKsntmxRd3TaJFYM2f8");
boolean testnet = WalletManager.getInstance().isTestNet();
if (!testnet) throw new IllegalStateException("Sending TX only on testnet. sorry.");
Helper.showKeyboard(getActivity());
etAddress.requestFocus();
etAddress.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@@ -191,6 +191,21 @@ public class SendFragment extends Fragment {
}
});
bDispose.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activityCallback.onDisposeRequest();
enableEdit();
}
});
bAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
activityCallback.onScanAddress();
}
});
bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -224,6 +239,26 @@ public class SendFragment extends Fragment {
@Override
public void onClick(View v) {
bSend.setEnabled(false);
boolean testnet = WalletManager.getInstance().isTestNet();
if (testnet) {
send();
} else {
etNotes.setEnabled(false);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
bReallySend.setVisibility(View.VISIBLE);
}
}, 1000);
}
}
});
bReallySend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
bReallySend.setEnabled(false);
send();
}
});
@@ -240,11 +275,7 @@ public class SendFragment extends Fragment {
private boolean addressOk() {
String address = etAddress.getText().toString();
if (WalletManager.getInstance().isTestNet()) {
return ((address.length() == 95) && ("9A".indexOf(address.charAt(0)) >= 0));
} else {
return ((address.length() == 95) && ("4".indexOf(address.charAt(0)) >= 0));
}
return Helper.isAddressOk(address, WalletManager.getInstance().isTestNet());
}
private boolean amountOk() {
@@ -304,6 +335,10 @@ public class SendFragment extends Fragment {
bPaymentId.setEnabled(true);
bSweep.setEnabled(true);
bPrepareSend.setEnabled(true);
llConfirmSend.setVisibility(View.GONE);
bSend.setEnabled(true);
etNotes.setEnabled(true);
bReallySend.setVisibility(View.GONE);
}
private void send() {
@@ -327,6 +362,37 @@ public class SendFragment extends Fragment {
String getWalletAddress();
void onDisposeRequest();
void onScanAddress();
BarcodeData getScannedData();
}
@Override
public void onResume() {
super.onResume();
BarcodeData data = activityCallback.getScannedData();
if (data != null) {
String scannedAddress = data.address;
if (scannedAddress != null) {
etAddress.setText(scannedAddress);
}
String scannedPaymenId = data.paymentId;
if (scannedPaymenId != null) {
etPaymentId.setText(scannedPaymenId);
}
}
// jump to first empty field
if (etAddress.getText().toString().isEmpty()) {
etAddress.requestFocus();
} else if (etPaymentId.getText().toString().isEmpty()) {
etPaymentId.requestFocus();
} else {
etAmount.requestFocus();
}
Log.d(TAG, "onResume");
}
@Override
@@ -350,7 +416,6 @@ public class SendFragment extends Fragment {
tvTxAmount.setText(Wallet.getDisplayAmount(pendingTransaction.getAmount()));
tvTxFee.setText(Wallet.getDisplayAmount(pendingTransaction.getFee()));
tvTxDust.setText(Wallet.getDisplayAmount(pendingTransaction.getDust()));
bSend.setEnabled(true);
}
public void onCreatedTransactionFailed(String errorText) {

View File

@@ -41,6 +41,8 @@ import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;
public class TxFragment extends Fragment {
@@ -48,7 +50,7 @@ public class TxFragment extends Fragment {
static public final String ARG_INFO = "info";
private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
public TxFragment() {
super();
@@ -60,6 +62,7 @@ public class TxFragment extends Fragment {
TextView tvTxTimestamp;
TextView tvTxId;
TextView tvTxKey;
TextView tvDestination;
TextView tvTxPaymentId;
TextView tvTxBlockheight;
TextView tvTxAmount;
@@ -78,6 +81,7 @@ public class TxFragment extends Fragment {
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
tvTxKey = (TextView) view.findViewById(R.id.tvTxKey);
tvDestination = (TextView) view.findViewById(R.id.tvDestination);
tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId);
tvTxBlockheight = (TextView) view.findViewById(R.id.tvTxBlockheight);
tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount);
@@ -142,10 +146,10 @@ public class TxFragment extends Fragment {
sb.append(getString(R.string.tx_timestamp)).append(": ");
sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n");
sb.append(getString(R.string.tx_blockheight)).append(": ");
if (info.isPending) {
sb.append(getString(R.string.tx_pending)).append("\n");
} else if (info.isFailed) {
if (info.isFailed) {
sb.append(getString(R.string.tx_failed)).append("\n");
} else if (info.isPending) {
sb.append(getString(R.string.tx_pending)).append("\n");
} else {
sb.append(info.blockheight).append("\n");
}
@@ -154,11 +158,11 @@ public class TxFragment extends Fragment {
boolean comma = false;
for (Transfer transfer : info.transfers) {
if (comma) {
sb.append(",");
sb.append(", ");
} else {
comma = true;
}
sb.append("[").append(transfer.address.substring(0, 6)).append("] ");
sb.append(transfer.address).append(": ");
sb.append(Wallet.getDisplayAmount(transfer.amount));
}
} else {
@@ -192,20 +196,23 @@ public class TxFragment extends Fragment {
tvTxId.setText(info.hash);
tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey);
tvTxPaymentId.setText(info.paymentId);
if (info.isPending) {
tvTxBlockheight.setText(getString(R.string.tx_pending));
} else if (info.isFailed) {
if (info.isFailed) {
tvTxBlockheight.setText(getString(R.string.tx_failed));
} else if (info.isPending) {
tvTxBlockheight.setText(getString(R.string.tx_pending));
} else {
tvTxBlockheight.setText("" + info.blockheight);
}
String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-");
tvTxAmount.setText(sign + Wallet.getDisplayAmount(info.amount));
tvTxFee.setText(Wallet.getDisplayAmount(info.fee));
Set<String> destinations = new HashSet<>();
StringBuffer sb = new StringBuffer();
StringBuffer dstSb = new StringBuffer();
if (info.transfers != null) {
boolean newline = false;
for (Transfer transfer : info.transfers) {
destinations.add(transfer.address);
if (newline) {
sb.append("\n");
} else {
@@ -214,10 +221,21 @@ public class TxFragment extends Fragment {
sb.append("[").append(transfer.address.substring(0, 6)).append("] ");
sb.append(Wallet.getDisplayAmount(transfer.amount));
}
newline = false;
for (String dst : destinations) {
if (newline) {
dstSb.append("\n");
} else {
newline = true;
}
dstSb.append(dst);
}
} else {
sb.append("-");
dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ? activityCallback.getWalletAddress() : "-");
}
tvTxTransfers.setText(sb.toString());
tvDestination.setText(dstSb.toString());
this.info = info;
bCopy.setEnabled(true);
}

View File

@@ -16,7 +16,6 @@
package com.m2049r.xmrwallet;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
@@ -24,13 +23,17 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.View;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;
import com.m2049r.xmrwallet.model.PendingTransaction;
@@ -38,19 +41,20 @@ import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.BarcodeData;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.TxData;
public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener,
WalletService.Observer, SendFragment.Listener, TxFragment.Listener, GenerateReviewFragment.ListenerWithWallet {
WalletService.Observer, SendFragment.Listener, TxFragment.Listener,
GenerateReviewFragment.ListenerWithWallet,
ScannerFragment.Listener {
private static final String TAG = "WalletActivity";
static final int MIN_DAEMON_VERSION = 65544;
public static final String REQUEST_ID = "id";
public static final String REQUEST_PW = "pw";
Toolbar tbWallet;
Toolbar toolbar;
private boolean synced = false;
@@ -110,6 +114,27 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (!haveWallet) return true;
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.wallet_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_info:
onWalletDetails();
break;
default:
break;
}
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
@@ -119,15 +144,15 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
return;
}
tbWallet = (Toolbar) findViewById(R.id.tbWallet);
tbWallet.setTitle(R.string.app_name);
setSupportActionBar(tbWallet);
tbWallet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onWalletDetails();
}
});
toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(R.string.app_name);
setSupportActionBar(toolbar);
boolean testnet = WalletManager.getInstance().isTestNet();
if (testnet) {
toolbar.setBackgroundResource(R.color.colorPrimaryDark);
} else {
toolbar.setBackgroundResource(R.color.moneroOrange);
}
Fragment walletFragment = new WalletFragment();
getFragmentManager().beginTransaction()
@@ -161,6 +186,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
String walletId = extras.getString(REQUEST_ID);
if (walletId != null) {
setTitle(walletId);
setSubtitle("");
}
}
updateProgress();
@@ -175,6 +201,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
// see this happen.
mBoundService = null;
setTitle(getString(R.string.wallet_activity_name));
setSubtitle("");
Log.d(TAG, "DISCONNECTED");
}
};
@@ -270,7 +297,12 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
@Override
public void setTitle(String title) {
tbWallet.setTitle(title);
toolbar.setTitle(title);
}
@Override
public void setSubtitle(String subtitle) {
toolbar.setSubtitle(subtitle);
}
@Override
@@ -306,6 +338,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
final WalletFragment walletFragment = (WalletFragment)
getFragmentManager().findFragmentById(R.id.fragment_container);
if (wallet.isSynchronized()) {
Log.d(TAG, "onRefreshed() synced");
releaseWakeLock(); // the idea is to stay awake until synced
if (!synced) {
onProgress(null);
@@ -345,6 +378,25 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
});
}
boolean haveWallet = false;
@Override
public void onWalletStarted(final boolean success) {
runOnUiThread(new Runnable() {
public void run() {
if (!success) {
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_connect_failed), Toast.LENGTH_LONG).show();
}
}
});
if (!success) {
finish();
} else {
haveWallet = true;
invalidateOptionsMenu();
}
}
@Override
public void onCreatedTransaction(final PendingTransaction pendingTransaction) {
try {
@@ -539,10 +591,71 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
private void onWalletDetails() {
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof WalletFragment) {
if (!(fragment instanceof GenerateReviewFragment)) {
Bundle extras = new Bundle();
extras.putString("type", GenerateReviewFragment.VIEW_WALLET);
replaceFragment(new GenerateReviewFragment(), null, extras);
}
}
@Override
public void onDisposeRequest() {
getWallet().disposePendingTransaction();
}
private void startScanFragment() {
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof SendFragment) {
Bundle extras = new Bundle();
replaceFragment(new ScannerFragment(), null, extras);
}
}
/// QR scanner callbacks
@Override
public void onScanAddress() {
if (Helper.getCameraPermission(this)) {
startScanFragment();
} else {
Log.i(TAG, "Waiting for permissions");
}
}
private BarcodeData scannedData = null;
@Override
public void onAddressScanned(String address, String paymentId) {
// Log.d(TAG, "got " + address);
scannedData = new BarcodeData(address, paymentId);
popFragmentStack(null);
}
@Override
public BarcodeData getScannedData() {
BarcodeData data = scannedData;
scannedData = null;
return data;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Log.d(TAG, "onRequestPermissionsResult()");
switch (requestCode) {
case Helper.PERMISSIONS_REQUEST_CAMERA:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startScanFragment();
} else {
String msg = getString(R.string.message_camera_not_permitted);
Log.e(TAG, msg);
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}
break;
default:
}
}
}

View File

@@ -22,6 +22,7 @@ import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.constraint.ConstraintLayout;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.RecyclerView;
@@ -29,6 +30,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
@@ -38,6 +40,7 @@ import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import java.text.NumberFormat;
import java.util.List;
@@ -48,10 +51,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
private NumberFormat formatter = NumberFormat.getInstance();
TextView tvBalance;
TextView tvUnlockedBalance;
LinearLayout llUnconfirmedAmount;
TextView tvUnconfirmedAmount;
TextView tvBlockHeightProgress;
TextView tvConnectionStatus;
LinearLayout llProgress;
ConstraintLayout clProgress;
TextView tvProgress;
ProgressBar pbProgress;
Button bSend;
@@ -63,11 +66,13 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
tvProgress = (TextView) view.findViewById(R.id.tvProgress);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
llProgress = (LinearLayout) view.findViewById(R.id.llProgress);
clProgress = (ConstraintLayout) view.findViewById(R.id.clProgress);
llUnconfirmedAmount = (LinearLayout) view.findViewById(R.id.llUnconfirmedAmount);
tvBalance = (TextView) view.findViewById(R.id.tvBalance);
tvUnlockedBalance = (TextView) view.findViewById(R.id.tvUnlockedBalance);
tvBalance.setText(getDisplayAmount(0));
tvUnconfirmedAmount = (TextView) view.findViewById(R.id.tvUnconfirmedAmount);
tvUnconfirmedAmount.setText(getDisplayAmount(0));
tvBlockHeightProgress = (TextView) view.findViewById(R.id.tvBlockHeightProgress);
tvConnectionStatus = (TextView) view.findViewById(R.id.tvConnectionStatus);
bSend = (Button) view.findViewById(R.id.bSend);
@@ -144,11 +149,11 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
}
public void showProgress() {
llProgress.setVisibility(View.VISIBLE);
clProgress.setVisibility(View.VISIBLE);
}
public void hideProgress() {
llProgress.setVisibility(View.GONE);
clProgress.setVisibility(View.GONE);
}
String setActivityTitle(Wallet wallet) {
@@ -157,10 +162,12 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
if (shortName.length() > 16) {
shortName = shortName.substring(0, 14) + "...";
}
String title = "[" + wallet.getAddress().substring(0, 6) + "] "
+ shortName
+ (wallet.isWatchOnly() ? " " + getString(R.string.watchonly_label) : "");
String title = "[" + wallet.getAddress().substring(0, 6) + "] " + shortName;
activityCallback.setTitle(title);
String watchOnly = (wallet.isWatchOnly() ? " " + getString(R.string.watchonly_label) : "");
String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
activityCallback.setSubtitle(net + " " + watchOnly);
Log.d(TAG, "wallet title is " + title);
return title;
}
@@ -168,14 +175,39 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
private long firstBlock = 0;
private String walletTitle = null;
private String getDisplayAmount(long amount) {
String s = Wallet.getDisplayAmount(amount);
int lastZero = 0;
int decimal = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if ((lastZero == 0) && (s.charAt(i) != '0')) lastZero = i + 1;
// TODO i18n
if (s.charAt(i) == '.') {
decimal = i;
break;
}
}
//Log.d(TAG, decimal + "/" + lastZero + "/" + s);
int cutoff = Math.max(lastZero, decimal + 2);
return s.substring(0, cutoff);
}
private void updateStatus(Wallet wallet) {
Log.d(TAG, "updateStatus()");
if (walletTitle == null) {
walletTitle = setActivityTitle(wallet);
onProgress(100); // of loading
}
tvBalance.setText(Wallet.getDisplayAmount(wallet.getBalance()));
tvUnlockedBalance.setText(Wallet.getDisplayAmount(wallet.getUnlockedBalance()));
long balance = wallet.getBalance();
long unlockedBalance = wallet.getUnlockedBalance();
tvBalance.setText(getDisplayAmount(unlockedBalance));
tvUnconfirmedAmount.setText(getDisplayAmount(balance - unlockedBalance));
// balance cannot be less than unlockedBalance
/*if (balance != unlockedBalance) {
llPendingAmount.setVisibility(View.VISIBLE);
} else {
llPendingAmount.setVisibility(View.INVISIBLE);
}*/
String sync = "";
if (!activityCallback.hasBoundService())
throw new IllegalStateException("WalletService not bound.");
@@ -196,9 +228,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight());
}
}
String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
tvBlockHeightProgress.setText(sync);
tvConnectionStatus.setText(net + " " + daemonConnected.toString().substring(17));
//String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
//activityCallback.setSubtitle(net + " " + daemonConnected.toString().substring(17));
// TODO show connected status somewhere
}
Listener activityCallback;
@@ -215,6 +248,8 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
void setTitle(String title);
void setSubtitle(String subtitle);
void onSendRequest();
void onTxDetailsRequest(TransactionInfo info);

View File

@@ -40,8 +40,7 @@ import java.util.TimeZone;
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
private static final String TAG = "TransactionInfoAdapter";
private final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
private final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
static final int TX_RED = Color.rgb(255, 79, 65);
static final int TX_GREEN = Color.rgb(54, 176, 91);
@@ -60,8 +59,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
this.listener = listener;
Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone(); //get the local time zone.
DATE_FORMATTER.setTimeZone(tz);
TIME_FORMATTER.setTimeZone(tz);
DATETIME_FORMATTER.setTimeZone(tz);
}
@Override
@@ -91,16 +89,15 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
Collections.sort(data, new Comparator<TransactionInfo>() {
@Override
public int compare(TransactionInfo o1, TransactionInfo o2) {
if ((o1.isPending) && (o2.isPending)) {
long b1 = o1.timestamp;
long b2 = o2.timestamp;
return (b1 > b2) ? -1 : (b1 < b2) ? 1 : 0;
long b1 = o1.timestamp;
long b2 = o2.timestamp;
if (b1 > b2) {
return -1;
} else if (b1 < b2) {
return 1;
} else {
return o1.hash.compareTo(o2.hash);
}
if (o1.isPending) return -1;
if (o2.isPending) return 1;
long b1 = o1.blockheight;
long b2 = o2.blockheight;
return (b1 > b2) ? -1 : (b1 < b2) ? 1 : 0;
}
});
this.infoItems.addAll(data);
@@ -114,8 +111,8 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
final TextView tvAmount;
final TextView tvAmountPoint;
final TextView tvAmountDecimal;
final TextView tvDate;
final TextView tvTime;
final TextView tvPaymentId;
final TextView tvDateTime;
TransactionInfo infoItem;
ViewHolder(View itemView) {
@@ -124,16 +121,12 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
// I know this is stupid but can't be bothered to align decimals otherwise
this.tvAmountPoint = (TextView) itemView.findViewById(R.id.tx_amount_point);
this.tvAmountDecimal = (TextView) itemView.findViewById(R.id.tx_amount_decimal);
this.tvDate = (TextView) itemView.findViewById(R.id.tx_date);
this.tvTime = (TextView) itemView.findViewById(R.id.tx_time);
this.tvPaymentId = (TextView) itemView.findViewById(R.id.tx_paymentid);
this.tvDateTime = (TextView) itemView.findViewById(R.id.tx_datetime);
}
private String getDate(long time) {
return DATE_FORMATTER.format(new Date(time * 1000));
}
private String getTime(long time) {
return TIME_FORMATTER.format(new Date(time * 1000));
private String getDateTime(long time) {
return DATETIME_FORMATTER.format(new Date(time * 1000));
}
private void setTxColour(int clr) {
@@ -147,25 +140,26 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
String displayAmount = Wallet.getDisplayAmount(infoItem.amount);
// TODO fix this with i8n code but cryptonote::print_money always uses '.' for decimal point
String amountParts[] = displayAmount.split("\\.");
amountParts[1] = amountParts[1].substring(0,5);
this.tvAmount.setText(amountParts[0]);
this.tvAmountDecimal.setText(amountParts[1]);
if (infoItem.isPending) {
if (infoItem.isFailed) {
this.tvAmount.setText('(' + amountParts[0]);
this.tvAmountDecimal.setText(amountParts[1] + ')');
setTxColour(TX_FAILED);
} else if (infoItem.isPending) {
setTxColour(TX_PENDING);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
this.tvAmount.setText('-' + amountParts[0]);
}
} else if (infoItem.isFailed) {
this.tvAmount.setText('(' + amountParts[0]);
this.tvAmountDecimal.setText(amountParts[1] + ')');
setTxColour(TX_FAILED);
} else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(TX_GREEN);
} else {
setTxColour(TX_RED);
}
this.tvDate.setText(getDate(infoItem.timestamp));
this.tvTime.setText(getTime(infoItem.timestamp));
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000")?"":infoItem.paymentId);
this.tvDateTime.setText(getDateTime(infoItem.timestamp));
itemView.setOnClickListener(this);
}

View File

@@ -96,10 +96,13 @@ public class Wallet {
// virtual std::string keysFilename() const = 0;
public boolean init(long upper_transaction_size_limit) {
return initJ(WalletManager.getInstance().getDaemonAddress(), upper_transaction_size_limit);
return initJ(WalletManager.getInstance().getDaemonAddress(), upper_transaction_size_limit,
WalletManager.getInstance().getDaemonUsername(),
WalletManager.getInstance().getDaemonPassword());
}
private native boolean initJ(String daemon_address, long upper_transaction_size_limit);
private native boolean initJ(String daemon_address, long upper_transaction_size_limit,
String daemon_username, String daemon_password);
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
@@ -179,14 +182,15 @@ public class Wallet {
long amount, int mixin_count,
PendingTransaction.Priority priority) {
disposePendingTransaction();
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, priority);
int _priority = priority.getValue();
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority);
pendingTransaction = new PendingTransaction(txHandle);
return pendingTransaction;
}
private native long createTransactionJ(String dst_addr, String payment_id,
long mount, int mixin_count,
PendingTransaction.Priority priority);
long amount, int mixin_count,
int priority);
public PendingTransaction createSweepUnmixableTransaction() {

View File

@@ -197,9 +197,12 @@ public class WalletManager {
return testnet;
}
public void setDaemon(String address, boolean testnet) {
public void setDaemon(String address, boolean testnet, String username, String password) {
//Log.d(TAG, "SETDAEMON " + username + "/" + password + "/" + address);
this.daemonAddress = address;
this.testnet = testnet;
this.daemonUsername = username;
this.daemonPassword = password;
setDaemonAddressJ(address);
}
@@ -213,6 +216,18 @@ public class WalletManager {
private native void setDaemonAddressJ(String address);
String daemonUsername = "";
public String getDaemonUsername() {
return daemonUsername;
}
String daemonPassword = "";
public String getDaemonPassword() {
return daemonPassword;
}
public native int getDaemonVersion();
public native long getBlockchainHeight();

View File

@@ -16,8 +16,13 @@
package com.m2049r.xmrwallet.service;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -25,9 +30,11 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.WalletActivity;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.TransactionHistory;
import com.m2049r.xmrwallet.model.Wallet;
@@ -38,6 +45,7 @@ import com.m2049r.xmrwallet.util.TxData;
public class WalletService extends Service {
final static String TAG = "WalletService";
final static int NOTIFICATION_ID = 2049;
public static final String REQUEST_WALLET = "wallet";
public static final String REQUEST = "request";
@@ -119,6 +127,7 @@ public class WalletService extends Service {
boolean fullRefresh = false;
updateDaemonState(wallet, wallet.isSynchronized() ? height : 0);
if (!wallet.isSynchronized()) {
updated = true;
// we want to see our transactions as they come in
wallet.getHistory().refresh();
int txCount = wallet.getHistory().getCount();
@@ -216,6 +225,8 @@ public class WalletService extends Service {
void onSentTransaction(boolean success);
void onSetNotes(boolean success);
void onWalletStarted(boolean success);
}
String progressText = null;
@@ -255,6 +266,8 @@ public class WalletService extends Service {
private Looper mServiceLooper;
private WalletService.ServiceHandler mServiceHandler;
private boolean errorState = false;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
ServiceHandler(Looper looper) {
@@ -264,6 +277,11 @@ public class WalletService extends Service {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "Handling " + msg.arg2);
if (errorState) {
Log.i(TAG, "In error state.");
// also, we have already stopped ourselves
return;
}
switch (msg.arg2) {
case START_SERVICE: {
Bundle extras = msg.getData();
@@ -275,7 +293,12 @@ public class WalletService extends Service {
if (walletId != null) {
showProgress(getString(R.string.status_wallet_loading));
showProgress(10);
start(walletId, walletPw); // TODO What if this fails?
boolean success = start(walletId, walletPw);
if (observer != null) observer.onWalletStarted(success);
if (!success) {
errorState = true;
stop();
}
}
} else if (cmd.equals(REQUEST_CMD_STORE)) {
Wallet myWallet = getWallet();
@@ -319,10 +342,6 @@ public class WalletService extends Service {
} else if (cmd.equals(REQUEST_CMD_SEND)) {
Wallet myWallet = getWallet();
Log.d(TAG, "SEND TX for wallet: " + myWallet.getName());
if (!myWallet.isTestNet()) {
Log.e(TAG, "Sending transactions only on testnet");
throw new IllegalStateException("Sending transactions only in testnet");
}
PendingTransaction pendingTransaction = myWallet.getPendingTransaction();
if (pendingTransaction.getStatus() != PendingTransaction.Status.Status_Ok) {
Log.e(TAG, "PendingTransaction is " + pendingTransaction.getStatus());
@@ -432,7 +451,7 @@ public class WalletService extends Service {
msg.setData(intent.getExtras());
mServiceHandler.sendMessage(msg);
//Log.d(TAG, "onStartCommand() message sent");
return START_NOT_STICKY;
return START_STICKY;
}
@Override
@@ -453,7 +472,8 @@ public class WalletService extends Service {
return true; // true is important so that onUnbind is also called next time
}
private void start(String walletName, String walletPassword) {
private boolean start(String walletName, String walletPassword) {
startNotfication();
// if there is an listener it is always started / syncing
Log.d(TAG, "start()");
showProgress(getString(R.string.status_wallet_loading));
@@ -461,7 +481,10 @@ public class WalletService extends Service {
if (listener == null) {
Log.d(TAG, "start() loadWallet");
Wallet aWallet = loadWallet(walletName, walletPassword);
// TODO check aWallet and die gracefully if neccessary
if ((aWallet == null) || (aWallet.getConnectionStatus() != Wallet.ConnectionStatus.ConnectionStatus_Connected)) {
if (aWallet != null) aWallet.close();
return false;
}
listener = new MyWalletListener(aWallet);
listener.start();
showProgress(100);
@@ -471,6 +494,7 @@ public class WalletService extends Service {
// if we try to refresh the history here we get occasional segfaults!
// doesnt matter since we update as soon as we get a new block anyway
Log.d(TAG, "start() done");
return true;
}
public void stop() {
@@ -488,6 +512,7 @@ public class WalletService extends Service {
Log.d(TAG, "stop() closed");
listener = null;
}
stopForeground(true);
stopSelf();
}
@@ -502,7 +527,7 @@ public class WalletService extends Service {
showProgress(55);
wallet.init(0);
showProgress(90);
Log.d(TAG, wallet.getConnectionStatus().toString());
//Log.d(TAG, wallet.getConnectionStatus().toString());
}
return wallet;
}
@@ -532,4 +557,16 @@ public class WalletService extends Service {
}
return wallet;
}
private void startNotfication() {
Intent notificationIntent = new Intent(this, WalletActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new Notification.Builder(this)
.setContentTitle(getString(R.string.service_description))
.setSmallIcon(R.drawable.ic_notification_sync_32_32)
.setContentIntent(pendingIntent)
.build();
startForeground(NOTIFICATION_ID, notification);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
public class BarcodeData {
public String address = null;
public String paymentId = null;
//String amount = null:
public BarcodeData(String address, String paymentId) {
this.address = address;
this.paymentId = paymentId;
}
}

View File

@@ -27,6 +27,7 @@ import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.WalletManager;
import java.io.File;
@@ -71,6 +72,24 @@ public class Helper {
}
}
public static final int PERMISSIONS_REQUEST_CAMERA = 1;
static public boolean getCameraPermission(Activity context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.CAMERA)
== PackageManager.PERMISSION_DENIED) {
Log.d(TAG, "Permission denied for CAMERA - requesting it");
String[] permissions = {Manifest.permission.CAMERA};
context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA);
return false;
} else {
return true;
}
} else {
return true;
}
}
static public String getWalletPath(Context context, String aWalletName) {
File walletDir = getStorageRoot(context);
//d(TAG, "walletdir=" + walletDir.getAbsolutePath());
@@ -107,4 +126,14 @@ public class Helper {
static public void hideKeyboardAlways(Activity act) {
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
}
static public boolean isAddressOk(String address, boolean testnet) {
if (address == null) return false;
if (testnet) {
return ((address.length() == 95) && ("9A".indexOf(address.charAt(0)) >= 0));
} else {
return ((address.length() == 95) && ("4".indexOf(address.charAt(0)) >= 0));
}
}
}

View File

@@ -75,4 +75,19 @@ public class TxData implements Parcelable {
return 0;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("dst_addr:");
sb.append(dst_addr);
sb.append(",paymentId:");
sb.append(paymentId);
sb.append(",amount:");
sb.append(amount);
sb.append(",mixin:");
sb.append(mixin);
sb.append(",priority:");
sb.append(priority.toString());
return sb.toString();
}
}

View File

@@ -5,5 +5,5 @@
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-.25 1.97,-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01.25,-1.97.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z" />
</vector>

View File

@@ -4,6 +4,20 @@
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8sp"
android:orientation="vertical">
<ProgressBar
android:id="@+id/pbProgress"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -12,7 +26,7 @@
<TextView
android:id="@+id/tvWalletLabel"
android:layout_width="0dp"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/generate_wallet_label"
@@ -22,7 +36,7 @@
<TextView
android:id="@+id/tvWalletPasswordLabel"
android:layout_width="0dp"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/generate_password_label"
@@ -58,20 +72,6 @@
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical">
<ProgressBar
android:id="@+id/pbProgress"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible" />
</LinearLayout>
<TextView
android:id="@+id/tvWalletMnemonicLabel"
android:layout_width="match_parent"
@@ -103,10 +103,10 @@
android:id="@+id/tvWalletAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:selectAllOnFocus="true"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textIsSelectable="true"
android:selectAllOnFocus="true"
android:textSize="16sp" />
<TextView
@@ -123,10 +123,10 @@
android:id="@+id/tvWalletViewKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:selectAllOnFocus="true"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textIsSelectable="true"
android:selectAllOnFocus="true"
android:textSize="16sp" />
<TextView
@@ -143,10 +143,10 @@
android:id="@+id/tvWalletSpendKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:selectAllOnFocus="true"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textIsSelectable="true"
android:selectAllOnFocus="true"
android:textSize="16sp" />
<Button
@@ -157,43 +157,4 @@
android:background="@color/colorPrimary"
android:text="@string/generate_button_accept"
android:visibility="gone" />
<LinearLayout
android:id="@+id/llFunctions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical"
android:visibility="gone">
<View
android:layout_width="match_parent"
android:layout_height="2dip"
android:background="@color/colorPrimary" />
<Button
android:id="@+id/bBackup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/colorPrimaryDark"
android:text="@string/generate_button_backup" />
<Button
android:id="@+id/bExport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/colorPrimaryDark"
android:text="@string/generate_button_export" />
<Button
android:id="@+id/bDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@color/colorAccent"
android:text="@string/generate_button_delete" />
</LinearLayout>
</LinearLayout>

View File

@@ -6,7 +6,7 @@
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/tbLogin"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
@@ -14,6 +14,7 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_margin="4sp"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -17,35 +17,49 @@
android:textOff="@string/connect_testnet"
android:textOn="@string/connect_mainnet"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
</ToggleButton>
app:layout_constraintTop_toTopOf="parent"/>
<EditText
android:id="@+id/etDaemonAddress"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:gravity="center"
android:hint="@string/prompt_daemon"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"
android:inputType="text"
android:gravity="center"
android:hint="@string/prompt_daemon"
android:imeOptions="actionDone"
android:inputType="textWebEmailAddress|textNoSuggestions"
android:maxLines="1"
android:textIsSelectable="true"
android:textSize="15sp"
android:maxLines="1"
android:imeOptions="actionDone"
app:layout_constraintBaseline_toBaselineOf="@+id/tbMainNet"
app:layout_constraintRight_toLeftOf="@+id/tbMainNet"
app:layout_constraintLeft_toLeftOf="parent" />
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/tbMainNet" />
</android.support.constraint.ConstraintLayout>
<ListView
android:id="@+id/list"
android:layout_height="wrap_content"
<FrameLayout
android:layout_width="match_parent"
android:layout_marginTop="4dp">
</ListView>
android:layout_height="match_parent">
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"/>
<android.support.design.widget.FloatingActionButton xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/fabAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="16sp"
android:src="@drawable/ic_add_black_24dp"
app:elevation="6dp"
app:fabSize="auto"
app:pressedTranslationZ="12dp" />
</FrameLayout>
</LinearLayout>

File diff suppressed because it is too large Load Diff

View File

@@ -1,96 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card"
android:layout_width="match_parent"
app:cardBackgroundColor="@color/main_background"
android:layout_height="wrap_content"
android:layout_margin="1dp">
android:layout_margin="1sp"
app:cardBackgroundColor="@color/main_background">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8sp"
android:layout_marginTop="8sp"
tools:layout_editor_absoluteX="0sp"
tools:layout_editor_absoluteY="0sp">
<TextView
android:id="@+id/tx_amount"
android:layout_width="60sp"
android:layout_height="wrap_content"
tools:layout_editor_absoluteY="0dp"
tools:layout_editor_absoluteX="0dp">
android:gravity="end"
android:textSize="14sp"
android:textStyle="bold"
android:text="9999999"
android:textColor="@android:color/holo_red_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tx_amount"
android:gravity="end"
android:layout_width="70sp"
android:layout_height="wrap_content"
android:text="99999999"
android:textColor="@android:color/holo_red_light"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tx_amount_point"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:text="."
android:textColor="@android:color/holo_red_light"
app:layout_constraintBaseline_toBaselineOf="@+id/tx_amount"
app:layout_constraintLeft_toRightOf="@+id/tx_amount"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tx_amount_point"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="."
android:textColor="@android:color/holo_red_light"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/tx_amount"
android:layout_marginLeft="0dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tx_amount_decimal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="99999"
android:textColor="@android:color/holo_red_light"
app:layout_constraintBaseline_toBaselineOf="@+id/tx_amount"
app:layout_constraintLeft_toRightOf="@+id/tx_amount_point"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tx_amount_decimal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="999999999999"
android:textColor="@android:color/holo_red_light"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintLeft_toRightOf="@+id/tx_amount_point"
android:layout_marginLeft="0dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/tx_paymentid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8sp"
android:textSize="13sp"
android:text="0123456789abcdef"
app:layout_constraintBaseline_toBaselineOf="@+id/tx_amount"
app:layout_constraintRight_toLeftOf="@+id/tx_datetime"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tx_date"
android:textColor="@android:color/black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="2017-05-22"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
android:layout_marginLeft="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"
app:layout_constraintRight_toLeftOf="@+id/tx_time"
android:layout_marginRight="8dp"/>
<TextView
android:id="@+id/tx_datetime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="2017-05-22 21:32"
android:textColor="@android:color/black"
app:layout_constraintBaseline_toBaselineOf="@+id/tx_amount"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tx_time"
android:textColor="@android:color/black"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="21:32:11"
app:layout_constraintRight_toLeftOf="parent"
android:layout_marginRight="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="8dp"/>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
</android.support.v7.widget.CardView>

View File

@@ -46,6 +46,22 @@
android:textIsSelectable="true" />
</TableRow>
<TableRow>
<TextView
android:gravity="right"
android:padding="8dp"
android:text="@string/tx_destination"
android:textColor="@color/colorAccent" />
<TextView
android:id="@+id/tvDestination"
android:gravity="left"
android:padding="8dip"
android:selectAllOnFocus="true"
android:textIsSelectable="true" />
</TableRow>
<TableRow>
<TextView

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