Merge pull request #34 from m2049r/feature_qr_receive

QR Code for receiving
This commit is contained in:
m2049r 2017-09-02 12:56:17 +02:00 committed by GitHub
commit e8331dda7a
22 changed files with 759 additions and 118 deletions

View File

@ -11,7 +11,7 @@
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:icon="@drawable/ic_monero_32dp"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">

View File

@ -69,12 +69,9 @@ public class GenerateFragment extends Fragment {
bGenerate = (Button) view.findViewById(R.id.bGenerate);
etWalletMnemonic.setRawInputType(InputType.TYPE_CLASS_TEXT);
etWalletAddress.setRawInputType(InputType.TYPE_CLASS_TEXT);
etWalletViewKey.setRawInputType(InputType.TYPE_CLASS_TEXT);
etWalletSpendKey.setRawInputType(InputType.TYPE_CLASS_TEXT);
boolean testnet = WalletManager.getInstance().isTestNet();
//etWalletMnemonic.setTextIsSelectable(testnet);
etWalletAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etWalletViewKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etWalletSpendKey.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
Helper.showKeyboard(getActivity());
etWalletName.addTextChangedListener(new TextWatcher() {
@ -298,8 +295,8 @@ public class GenerateFragment extends Fragment {
private void generateWallet() {
String name = etWalletName.getText().toString();
if (name.length() == 0) return;
String walletPath = Helper.getWalletPath(getActivity(), name);
if (WalletManager.getInstance().walletExists(walletPath)) {
File walletFile = Helper.getWalletFile(getActivity(), name);
if (WalletManager.getInstance().walletExists(walletFile)) {
Toast.makeText(getActivity(), getString(R.string.generate_wallet_exists), Toast.LENGTH_LONG).show();
etWalletName.requestFocus();
return;
@ -348,7 +345,7 @@ public class GenerateFragment extends Fragment {
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onPause()");
Log.d(TAG, "onResume()");
activityCallback.setTitle(getString(R.string.generate_title));
}

View File

@ -79,10 +79,10 @@ public class GenerateReviewFragment extends Fragment {
Bundle b = getArguments();
String type = b.getString("type");
if (!type.equals(VIEW_WALLET)) {
String name = b.getString("name");
String path = b.getString("path");
String password = b.getString("password");
tvWalletName.setText(new File(name).getName());
show(name, password, type);
tvWalletName.setText(new File(path).getName());
show(path, password, type);
} else {
show(walletCallback.getWallet(), null, type);
}

View File

@ -85,8 +85,8 @@ public class LoginActivity extends AppCompatActivity
}
Log.d(TAG, "selected wallet is ." + walletName + ".");
// now it's getting real, check if wallet exists
String walletPath = Helper.getWalletPath(this, walletName);
if (WalletManager.getInstance().walletExists(walletPath)) {
File walletFile = Helper.getWalletFile(this, walletName);
if (WalletManager.getInstance().walletExists(walletFile)) {
promptPassword(walletName, new PasswordAction() {
@Override
public void action(String walletName, String password) {
@ -99,14 +99,30 @@ public class LoginActivity extends AppCompatActivity
}
@Override
public void onWalletDetails(final String walletName) {
public void onWalletDetails(String walletName) {
Log.d(TAG, "details for wallet ." + walletName + ".");
final String walletPath = Helper.getWalletPath(this, walletName);
if (WalletManager.getInstance().walletExists(walletPath)) {
final File walletFile = Helper.getWalletFile(this, walletName);
if (WalletManager.getInstance().walletExists(walletFile)) {
promptPassword(walletName, new PasswordAction() {
@Override
public void action(String walletName, String password) {
startDetails(walletPath, password, GenerateReviewFragment.VIEW_DETAILS);
startDetails(walletFile, password, GenerateReviewFragment.VIEW_DETAILS);
}
});
} else { // this cannot really happen as we prefilter choices
Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
}
}
@Override
public void onWalletReceive(String walletName) {
Log.d(TAG, "receive for wallet ." + walletName + ".");
final File walletFile = Helper.getWalletFile(this, walletName);
if (WalletManager.getInstance().walletExists(walletFile)) {
promptPassword(walletName, new PasswordAction() {
@Override
public void action(String walletName, String password) {
startReceive(walletFile, password);
}
});
} else { // this cannot really happen as we prefilter choices
@ -251,15 +267,23 @@ public class LoginActivity extends AppCompatActivity
startActivity(intent);
}
void startDetails(final String walletPath, final String password, String type) {
void startDetails(File walletFile, String password, String type) {
Log.d(TAG, "startDetails()");
Bundle b = new Bundle();
b.putString("name", walletPath);
b.putString("path", walletFile.getAbsolutePath());
b.putString("password", password);
b.putString("type", type);
startReviewFragment(b);
}
void startReceive(File walletFile, String password) {
Log.d(TAG, "startReceive()");
Bundle b = new Bundle();
b.putString("path", walletFile.getAbsolutePath());
b.putString("password", password);
startReceiveFragment(b);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Log.d(TAG, "onRequestPermissionsResult()");
@ -297,6 +321,11 @@ public class LoginActivity extends AppCompatActivity
Log.d(TAG, "GenerateReviewFragment placed");
}
void startReceiveFragment(Bundle extras) {
replaceFragment(new ReceiveFragment(), null, extras);
Log.d(TAG, "ReceiveFragment placed");
}
void replaceFragment(Fragment newFragment, String stackName, Bundle extras) {
if (extras != null) {
newFragment.setArguments(extras);
@ -345,29 +374,29 @@ public class LoginActivity extends AppCompatActivity
return;
}
String newWalletPath = new File(newWalletFolder, name).getAbsolutePath();
boolean success = walletCreator.createWallet(newWalletPath, password);
File newWalletFile = new File(newWalletFolder, name);
boolean success = walletCreator.createWallet(newWalletFile, password);
if (success) {
startDetails(newWalletPath, password, GenerateReviewFragment.VIEW_ACCEPT);
startDetails(newWalletFile, password, GenerateReviewFragment.VIEW_ACCEPT);
} else {
Toast.makeText(LoginActivity.this,
getString(R.string.generate_wallet_create_failed), Toast.LENGTH_LONG).show();
Log.e(TAG, "Could not create new wallet in " + newWalletPath);
Log.e(TAG, "Could not create new wallet in " + newWalletFile.getAbsolutePath());
}
}
interface WalletCreator {
boolean createWallet(String path, String password);
boolean createWallet(File aFile, String password);
}
@Override
public void onGenerate(String name, String password) {
createWallet(name, password,
new WalletCreator() {
public boolean createWallet(String path, String password) {
public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance()
.createWallet(path, password, MNEMONIC_LANGUAGE);
.createWallet(aFile, password, MNEMONIC_LANGUAGE);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) Log.e(TAG, newWallet.getErrorString());
newWallet.close();
@ -380,8 +409,8 @@ public class LoginActivity extends AppCompatActivity
public void onGenerate(String name, String password, final String seed, final long restoreHeight) {
createWallet(name, password,
new WalletCreator() {
public boolean createWallet(String path, String password) {
Wallet newWallet = WalletManager.getInstance().recoveryWallet(path, seed, restoreHeight);
public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance().recoveryWallet(aFile, seed, restoreHeight);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) Log.e(TAG, newWallet.getErrorString());
newWallet.setPassword(password);
@ -397,9 +426,9 @@ public class LoginActivity extends AppCompatActivity
final String address, final String viewKey, final String spendKey, final long restoreHeight) {
createWallet(name, password,
new WalletCreator() {
public boolean createWallet(String path, String password) {
public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance()
.createWalletFromKeys(path, MNEMONIC_LANGUAGE, restoreHeight,
.createWalletFromKeys(aFile, MNEMONIC_LANGUAGE, restoreHeight,
address, viewKey, spendKey);
boolean success = (newWallet.getStatus() == Wallet.Status.Status_Ok);
if (!success) Log.e(TAG, newWallet.getErrorString());

View File

@ -86,6 +86,8 @@ public class LoginFragment extends Fragment {
void onWalletDetails(final String wallet);
void onWalletReceive(final String wallet);
void onAddWallet();
void setTitle(String title);
@ -117,7 +119,7 @@ public class LoginFragment extends Fragment {
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onPause()");
Log.d(TAG, "onResume()");
activityCallback.setTitle(getString(R.string.login_activity_name));
}
@ -408,17 +410,18 @@ public class LoginFragment extends Fragment {
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
String listItem = (String) listView.getItemAtPosition(info.position);
switch (item.getItemId()) {
case R.id.action_info:
String listItem = (String) listView.getItemAtPosition(info.position);
return showInfo(listItem);
case R.id.action_receive:
return showReceive(listItem);
default:
return super.onContextItemSelected(item);
}
}
private boolean showInfo(String listItem) {
if (listItem.length() <= (WALLETNAME_PREAMBLE_LENGTH)) {
Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
return true;
@ -436,4 +439,23 @@ public class LoginFragment extends Fragment {
activityCallback.onWalletDetails(wallet);
return true;
}
private boolean showReceive(String listItem) {
if (listItem.length() <= (WALLETNAME_PREAMBLE_LENGTH)) {
Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
return true;
}
String wallet = listItem.substring(WALLETNAME_PREAMBLE_LENGTH);
String x = isMainNet() ? "4" : "9A";
if (x.indexOf(listItem.charAt(1)) < 0) {
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return true;
}
checkAndSetWalletDaemon("", !isMainNet()); // just set selected net
activityCallback.onWalletReceive(wallet);
return true;
}
}

View File

@ -0,0 +1,332 @@
/*
* 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.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
import com.m2049r.xmrwallet.util.Helper;
import java.util.HashMap;
import java.util.Map;
public class ReceiveFragment extends Fragment {
static final String TAG = "ReceiveFragment";
ProgressBar pbProgress;
TextView tvAddress;
EditText etPaymentId;
EditText etAmount;
Button bPaymentId;
Button bGenerate;
ImageView qrCode;
EditText etDummy;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.receive_fragment, container, false);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
tvAddress = (TextView) view.findViewById(R.id.tvAddress);
etPaymentId = (EditText) view.findViewById(R.id.etPaymentId);
etAmount = (EditText) view.findViewById(R.id.etAmount);
bPaymentId = (Button) view.findViewById(R.id.bPaymentId);
qrCode = (ImageView) view.findViewById(R.id.qrCode);
bGenerate = (Button) view.findViewById(R.id.bGenerate);
etDummy = (EditText) view.findViewById(R.id.etDummy);
etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
Helper.showKeyboard(getActivity());
etPaymentId.requestFocus();
etPaymentId.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (paymentIdOk()) {
etAmount.requestFocus();
} // otherwise ignore
return true;
}
return false;
}
});
etPaymentId.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
qrCode.setImageBitmap(getMoneroLogo());
if (paymentIdOk() && amountOk()) {
bGenerate.setEnabled(true);
} else {
bGenerate.setEnabled(false);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
etAmount.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
if (paymentIdOk() && amountOk()) {
Helper.hideKeyboard(getActivity());
generateQr();
}
return true;
}
return false;
}
});
etAmount.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable editable) {
qrCode.setImageBitmap(getMoneroLogo());
if (paymentIdOk() && amountOk()) {
bGenerate.setEnabled(true);
} else {
bGenerate.setEnabled(false);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
etPaymentId.setText((Wallet.generatePaymentId()));
etPaymentId.setSelection(etPaymentId.getText().length());
if (paymentIdOk() && amountOk()) {
generateQr();
}
}
});
bGenerate.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (paymentIdOk() && amountOk()) {
Helper.hideKeyboard(getActivity());
generateQr();
}
}
});
showProgress();
qrCode.setImageBitmap(getMoneroLogo());
Bundle b = getArguments();
String address = b.getString("address");
if (address == null) {
String path = b.getString("path");
String password = b.getString("password");
show(path, password);
} else {
show(address);
}
return view;
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
if (paymentIdOk() && amountOk() && tvAddress.getText().length() > 0) {
generateQr();
}
}
private void show(String address) {
tvAddress.setText(address);
etPaymentId.setEnabled(true);
etAmount.setEnabled(true);
bPaymentId.setEnabled(true);
bGenerate.setEnabled(true);
hideProgress();
generateQr();
}
private void show(final String walletPath, final String password) {
new Thread(null,
new Runnable() {
@Override
public void run() {
final Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password);
getActivity().runOnUiThread(new Runnable() {
public void run() {
show(wallet.getAddress());
wallet.close();
}
});
}
}
, "Receive", MoneroHandlerThread.THREAD_STACK_SIZE).start();
}
private boolean amountOk() {
String amountEntry = etAmount.getText().toString();
if (amountEntry.isEmpty()) return true;
long amount = Wallet.getAmountFromString(amountEntry);
return (amount > 0);
}
private boolean paymentIdOk() {
String paymentId = etPaymentId.getText().toString();
return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
}
private void generateQr() {
String address = tvAddress.getText().toString();
String paymentId = etPaymentId.getText().toString();
String enteredAmount = etAmount.getText().toString();
// that's a lot of converting ...
String amount = (enteredAmount.isEmpty() ? enteredAmount : Helper.getDisplayAmount(Wallet.getAmountFromString(enteredAmount)));
StringBuffer sb = new StringBuffer();
sb.append(ScannerFragment.QR_SCHEME).append(address);
boolean first = true;
if (!paymentId.isEmpty()) {
if (first) {
sb.append("?");
first = false;
}
sb.append(ScannerFragment.QR_PAYMENTID).append('=').append(paymentId);
}
if (!amount.isEmpty()) {
if (first) {
sb.append("?");
} else {
sb.append("&");
}
sb.append(ScannerFragment.QR_AMOUNT).append('=').append(amount);
}
String text = sb.toString();
Bitmap qr = generate(text, 500, 500);
if (qr != null) {
etAmount.setText(amount);
qrCode.setImageBitmap(qr);
etDummy.requestFocus();
}
}
public Bitmap generate(String text, int width, int height) {
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
try {
BitMatrix bitMatrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, width, height, hints);
int[] pixels = new int[width * height];
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
if (bitMatrix.get(j, i)) {
pixels[i * width + j] = 0x00000000;
} else {
pixels[i * height + j] = 0xffffffff;
}
}
}
Bitmap bitmap = Bitmap.createBitmap(pixels, 0, width, width, height, Bitmap.Config.RGB_565);
bitmap = addLogo(bitmap);
return bitmap;
} catch (WriterException e) {
e.printStackTrace();
}
return null;
}
// TODO check if we can sensibly cache some of this
private Bitmap addLogo(Bitmap qrBitmap) {
Bitmap logo = getMoneroLogo();
int qrWidth = qrBitmap.getWidth();
int qrHeight = qrBitmap.getHeight();
int logoWidth = logo.getWidth();
int logoHeight = logo.getHeight();
Bitmap logoBitmap = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(logoBitmap);
canvas.drawBitmap(qrBitmap, 0, 0, null);
canvas.save(Canvas.ALL_SAVE_FLAG);
// figure out how to scale the logo
float scaleSize = 1.0f;
while ((logoWidth / scaleSize) > (qrWidth / 5) || (logoHeight / scaleSize) > (qrHeight / 5)) {
scaleSize *= 2;
}
float sx = 1.0f / scaleSize;
canvas.scale(sx, sx, qrWidth / 2, qrHeight / 2);
canvas.drawBitmap(logo, (qrWidth - logoWidth) / 2, (qrHeight - logoHeight) / 2, null);
canvas.restore();
return logoBitmap;
}
private Bitmap logo = null;
private Bitmap getMoneroLogo() {
if (logo == null) {
logo = Helper.getBitmap(getContext(), R.drawable.ic_monero_qr);
}
return logo;
}
public void showProgress() {
pbProgress.setIndeterminate(true);
pbProgress.setVisibility(View.VISIBLE);
}
public void hideProgress() {
pbProgress.setVisibility(View.GONE);
}
}

View File

@ -100,8 +100,8 @@ public class SendFragment extends Fragment {
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
etAddress.setRawInputType(InputType.TYPE_CLASS_TEXT);
etPaymentId.setRawInputType(InputType.TYPE_CLASS_TEXT);
etAddress.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etPaymentId.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
Helper.showKeyboard(getActivity());
@ -209,7 +209,8 @@ public class SendFragment extends Fragment {
bPaymentId.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
etPaymentId.setText((activityCallback.generatePaymentId()));
etPaymentId.setText((Wallet.generatePaymentId()));
etPaymentId.setSelection(etPaymentId.getText().length());
}
});
@ -285,7 +286,7 @@ public class SendFragment extends Fragment {
private boolean paymentIdOk() {
String paymentId = etPaymentId.getText().toString();
return paymentId.isEmpty() || activityCallback.isPaymentIdValid(paymentId);
return paymentId.isEmpty() || Wallet.isPaymentIdValid(paymentId);
}
private void prepareSend() {
@ -358,10 +359,6 @@ public class SendFragment extends Fragment {
void onSend(String notes);
String generatePaymentId();
boolean isPaymentIdValid(String paymentId);
String getWalletAddress();
void onDisposeRequest();

View File

@ -47,6 +47,7 @@ import com.m2049r.xmrwallet.util.BarcodeData;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.TxData;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
@ -120,11 +121,8 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
if (!haveWallet) return true;
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.wallet_menu, menu);
return true;
public boolean hasWallet() {
return haveWallet;
}
@Override
@ -132,14 +130,16 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
switch (item.getItemId()) {
case R.id.action_info:
onWalletDetails();
break;
return true;
case R.id.action_receive:
onWalletReceive();
return true;
default:
break;
return super.onOptionsItemSelected(item);
}
return true;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
@ -561,16 +561,6 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
}
}
@Override
public String generatePaymentId() {
return getWallet().generatePaymentId();
}
@Override
public boolean isPaymentIdValid(String paymentId) {
return Wallet.isPaymentIdValid(paymentId);
}
@Override
public String getWalletAddress() {
return getWallet().getAddress();
@ -595,12 +585,9 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
}
private void onWalletDetails() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (!(fragment instanceof GenerateReviewFragment)) {
Bundle extras = new Bundle();
extras.putString("type", GenerateReviewFragment.VIEW_WALLET);
replaceFragment(new GenerateReviewFragment(), null, extras);
}
Bundle extras = new Bundle();
extras.putString("type", GenerateReviewFragment.VIEW_WALLET);
replaceFragment(new GenerateReviewFragment(), null, extras);
}
@Override
@ -610,11 +597,8 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
private void startScanFragment() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof SendFragment) {
Bundle extras = new Bundle();
replaceFragment(new ScannerFragment(), null, extras);
}
Bundle extras = new Bundle();
replaceFragment(new ScannerFragment(), null, extras);
}
/// QR scanner callbacks
@ -713,4 +697,21 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
}
}
@Override
public void onWalletReceive() {
startReceive(getWalletAddress());
}
void startReceive(String address) {
Log.d(TAG, "startReceive()");
Bundle b = new Bundle();
b.putString("address", address);
startReceiveFragment(b);
}
void startReceiveFragment(Bundle extras) {
replaceFragment(new ReceiveFragment(), null, extras);
Log.d(TAG, "ReceiveFragment placed");
}
}

View File

@ -18,12 +18,16 @@ package com.m2049r.xmrwallet;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.constraint.ConstraintLayout;
import android.support.v4.app.Fragment;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
@ -53,6 +57,19 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
ProgressBar pbProgress;
Button bSend;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (activityCallback.hasWallet())
inflater.inflate(R.menu.wallet_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@ -236,6 +253,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
boolean isWatchOnly();
String getTxKey(String txId);
void onWalletReceive();
boolean hasWallet();
}
@Override

View File

@ -71,8 +71,8 @@ public class WalletManager {
managedWallets.remove(walletId);
}
public Wallet createWallet(String path, String password, String language) {
long walletHandle = createWalletJ(path, password, language, isTestNet());
public Wallet createWallet(File aFile, String password, String language) {
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, isTestNet());
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet.getName(), wallet);
return wallet;
@ -89,14 +89,14 @@ public class WalletManager {
private native long openWalletJ(String path, String password, boolean isTestNet);
public Wallet recoveryWallet(String path, String mnemonic) {
Wallet wallet = recoveryWallet(path, mnemonic, 0);
public Wallet recoveryWallet(File aFile, String mnemonic) {
Wallet wallet = recoveryWallet(aFile, mnemonic, 0);
manageWallet(wallet.getName(), wallet);
return wallet;
}
public Wallet recoveryWallet(String path, String mnemonic, long restoreHeight) {
long walletHandle = recoveryWalletJ(path, mnemonic, isTestNet(), restoreHeight);
public Wallet recoveryWallet(File aFile, String mnemonic, long restoreHeight) {
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), mnemonic, isTestNet(), restoreHeight);
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet.getName(), wallet);
return wallet;
@ -104,9 +104,9 @@ public class WalletManager {
private native long recoveryWalletJ(String path, String mnemonic, boolean isTestNet, long restoreHeight);
public Wallet createWalletFromKeys(String path, String language, long restoreHeight,
public Wallet createWalletFromKeys(File aFile, String language, long restoreHeight,
String addressString, String viewKeyString, String spendKeyString) {
long walletHandle = createWalletFromKeysJ(path, language, isTestNet(), restoreHeight,
long walletHandle = createWalletFromKeysJ(aFile.getAbsolutePath(), language, isTestNet(), restoreHeight,
addressString, viewKeyString, spendKeyString);
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet.getName(), wallet);
@ -134,6 +134,10 @@ public class WalletManager {
return closed;
}
public boolean walletExists(File aFile) {
return walletExists(aFile.getAbsolutePath());
}
public native boolean walletExists(String path);
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
@ -146,6 +150,31 @@ public class WalletManager {
public String address;
}
public WalletInfo getWalletInfo(File wallet) {
WalletInfo info = new WalletInfo();
info.path = wallet.getParentFile();
info.name = wallet.getName();
File addressFile = new File(info.path, info.name + ".address.txt");
//Log.d(TAG, addressFile.getAbsolutePath());
info.address = "??????";
BufferedReader addressReader = null;
try {
addressReader = new BufferedReader(new FileReader(addressFile));
info.address = addressReader.readLine();
} catch (IOException ex) {
Log.d(TAG, ex.getLocalizedMessage());
} finally {
if (addressReader != null) {
try {
addressReader.close();
} catch (IOException ex) {
// that's just too bad
}
}
}
return info;
}
public List<WalletInfo> findWallets(File path) {
List<WalletInfo> wallets = new ArrayList<>();
Log.d(TAG, "Scanning: " + path.getAbsolutePath());
@ -155,29 +184,9 @@ public class WalletManager {
}
});
for (int i = 0; i < found.length; i++) {
WalletInfo info = new WalletInfo();
info.path = path;
String filename = found[i].getName();
info.name = filename.substring(0, filename.length() - 5); // 5 is length of ".keys"+1
File addressFile = new File(path, info.name + ".address.txt");
//Log.d(TAG, addressFile.getAbsolutePath());
info.address = "??????";
BufferedReader addressReader = null;
try {
addressReader = new BufferedReader(new FileReader(addressFile));
info.address = addressReader.readLine();
} catch (IOException ex) {
Log.d(TAG, ex.getLocalizedMessage());
} finally {
if (addressReader != null) {
try {
addressReader.close();
} catch (IOException ex) {
// that's just too bad
}
}
}
wallets.add(info);
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
wallets.add(getWalletInfo(f));
}
return wallets;
}

View File

@ -527,7 +527,7 @@ public class WalletService extends Service {
}
private Wallet openWallet(String walletName, String walletPassword) {
String path = Helper.getWalletPath(getApplicationContext(), walletName);
String path = Helper.getWalletFile(getApplicationContext(), walletName).getAbsolutePath();
showProgress(20);
Wallet wallet = null;
WalletManager walletMgr = WalletManager.getInstance();
@ -557,7 +557,7 @@ public class WalletService extends Service {
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new Notification.Builder(this)
.setContentTitle(getString(R.string.service_description))
.setSmallIcon(R.drawable.ic_notification_sync_32_32)
.setSmallIcon(R.drawable.ic_monero_32dp)
.setContentIntent(pendingIntent)
.build();
startForeground(NOTIFICATION_ID, notification);

View File

@ -21,7 +21,14 @@ import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.os.Environment;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
@ -91,12 +98,16 @@ public class Helper {
}
}
static public String getWalletPath(Context context, String aWalletName) {
// static public String getWalletPath(Context context, String aWalletName) {
// return getWalletFile(context, aWalletName).getAbsolutePath();
// }
static public File getWalletFile(Context context, String aWalletName) {
File walletDir = getStorageRoot(context);
//d(TAG, "walletdir=" + walletDir.getAbsolutePath());
File f = new File(walletDir, aWalletName);
Log.d(TAG, "wallet = " + f.getAbsolutePath() + " size=" + f.length());
return f.getAbsolutePath();
return f;
}
/* Checks if external storage is available for read and write */
@ -136,6 +147,7 @@ public class Helper {
return ((address.length() == 95) && ("4".indexOf(address.charAt(0)) >= 0));
}
}
static public String getDisplayAmount(long amount) {
String s = Wallet.getDisplayAmount(amount);
int lastZero = 0;
@ -152,4 +164,25 @@ public class Helper {
int cutoff = Math.max(lastZero, decimal + 2);
return s.substring(0, cutoff);
}
public static Bitmap getBitmap(Context context, int drawableId) {
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
if (drawable instanceof BitmapDrawable) {
return BitmapFactory.decodeResource(context.getResources(), drawableId);
} else if (drawable instanceof VectorDrawable) {
return getBitmap((VectorDrawable) drawable);
} else {
throw new IllegalArgumentException("unsupported drawable type");
}
}
private static Bitmap getBitmap(VectorDrawable vectorDrawable) {
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
vectorDrawable.draw(canvas);
return bitmap;
}
}

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportHeight="75.0"
android:viewportWidth="75.0">
<path
android:fillColor="#ff6600"
android:pathData="m 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" />
<path
android:fillColor="#4c4c4c"
android:pathData="m 21.3164,36.895994 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" />
</vector>

View File

@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportHeight="75.0"
android:viewportWidth="75.0">
<path
android:fillColor="#ffffff"
android:pathData=" M 37.3, 37.3
m -36.9, 0
a 36.9,36.9 0 1,0 73.8,0
a 36.9,36.9 0 1,0 -73.8,0" />
<path
android:fillColor="#ff6600"
android:pathData="m 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" />
<path
android:fillColor="#4c4c4c"
android:pathData="m 21.3164,36.895994 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" />
</vector>

View File

@ -0,0 +1,33 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="230.0"
android:viewportWidth="230.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M0 0L0 230L230 230L230 0L0 0z" />
<path
android:fillColor="#FF000000"
android:pathData="M10 10L10 80L80 80L80 10L10 10M110 10L110 20L120 20L120 10L110 10M130 10L130 30L140 30L140 10L130 10M150 10L150 80L220 80L220 10L150 10z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M20 20L20 70L70 70L70 20L20 20M160 20L160 70L210 70L210 20L160 20z" />
<path
android:fillColor="#FF000000"
android:pathData="M30 30L30 60L60 60L60 30L30 30M90 30L90 40L100 40L100 30L90 30M110 30L110 40L120 40L120 30L110 30M170 30L170 60L200 60L200 30L170 30M130 40L130 50L120 50L120 60L110 60L110 50L100 50L100 70L90 70L90 90L50 90L50 120L60 120L60 130L50 130L50 140L60 140L60 130L70 130L70 140L90 140L90 180L100 180L100 170L110 170L110 180L120 180L120 190L90 190L90 220L100 220L100 210L110 210L110 220L130 220L130 210L140 210L140 220L160 220L160 210L170 210L170 220L190 220L190 210L180 210L180 200L200 200L200 220L220 220L220 210L210 210L210 200L220 200L220 190L210 190L210 180L200 180L200 190L180 190L180 170L160 170L160 160L180 160L180 140L160 140L160 130L180 130L180 120L190 120L190 110L200 110L200 120L220 120L220 100L200 100L200 90L190 90L190 110L180 110L180 100L160 100L160 90L130 90L130 100L120 100L120 70L130 70L130 80L140 80L140 70L130 70L130 60L140 60L140 40L130 40z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M100 70L100 100L90 100L90 110L80 110L80 100L70 100L70 110L60 110L60 120L70 120L70 130L90 130L90 140L100 140L100 150L110 150L110 140L120 140L120 160L110 160L110 170L120 170L120 180L130 180L130 200L140 200L140 180L150 180L150 190L170 190L170 180L150 180L150 160L160 160L160 150L150 150L150 130L160 130L160 110L150 110L150 100L130 100L130 120L140 120L140 110L150 110L150 130L130 130L130 140L120 140L120 120L110 120L110 130L100 130L100 120L90 120L90 110L100 110L100 100L110 100L110 110L120 110L120 100L110 100L110 70L100 70z" />
<path
android:fillColor="#FF000000"
android:pathData="M10 90L10 120L30 120L30 130L10 130L10 140L30 140L30 130L40 130L40 120L30 120L30 110L20 110L20 100L40 100L40 90L10 90M70 110L70 120L80 120L80 110L70 110M200 130L200 140L190 140L190 150L200 150L200 170L210 170L210 160L220 160L220 150L210 150L210 130L200 130M10 150L10 220L80 220L80 150L10 150M130 150L130 160L140 160L140 150L130 150z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M20 160L20 210L70 210L70 160L20 160z" />
<path
android:fillColor="#FF000000"
android:pathData="M30 170L30 200L60 200L60 170L30 170M130 170L130 180L140 180L140 170L130 170z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M200 190L200 200L210 200L210 190L200 190M110 200L110 210L120 210L120 200L110 200M150 200L150 210L160 210L160 200L150 200z" />
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

View File

@ -4,19 +4,13 @@
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
<ProgressBar
android:id="@+id/pbProgress"
style="@android:style/Widget.ProgressBar.Horizontal"
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>
android:visibility="invisible" />
<LinearLayout
android:layout_width="match_parent"

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
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:layout_marginBottom="8sp"
android:visibility="gone" />
<TextView
android:id="@+id/tvAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4sp"
android:layout_marginTop="4sp"
android:hint="@string/send_address_hint"
android:textAlignment="center"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4sp"
android:layout_marginTop="4sp"
android:orientation="horizontal"
android:weightSum="10">
<TextView
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_marginRight="8sp"
android:layout_weight="3"
android:text="@string/receive_paymentid_label"
android:textAlignment="textEnd"
android:textSize="16sp" />
<EditText
android:id="@+id/etPaymentId"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="5"
android:enabled="false"
android:hint="@string/receive_paymentid_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="textStart"
android:textSize="16sp" />
<Button
android:id="@+id/bPaymentId"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="2"
android:background="@color/colorPrimary"
android:enabled="false"
android:minHeight="36sp"
android:text="@string/receive_paymentid_button"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4sp"
android:layout_marginTop="4sp"
android:orientation="horizontal"
android:weightSum="10">
<TextView
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_marginRight="8sp"
android:layout_weight="3"
android:text="@string/send_amount_label"
android:textAlignment="textEnd"
android:textSize="24sp" />
<EditText
android:id="@+id/etAmount"
android:layout_width="0sp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:enabled="false"
android:hint="@string/receive_amount_hint"
android:imeOptions="actionNext"
android:inputType="numberDecimal"
android:textAlignment="textStart"
android:textSize="24sp" />
</LinearLayout>
<Button
android:id="@+id/bGenerate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4sp"
android:layout_marginTop="4sp"
android:background="@color/colorPrimary"
android:enabled="false"
android:minHeight="36sp"
android:text="@string/receive_generate_hint" />
<ImageView
android:id="@+id/qrCode"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="16sp"
android:adjustViewBounds="true" />
<EditText
android:id="@+id/etDummy"
android:layout_width="0sp"
android:layout_height="0sp" />
</LinearLayout>

View File

@ -5,4 +5,8 @@
android:id="@+id/action_info"
android:title="@string/menu_info" />
<item
android:id="@+id/action_receive"
android:title="@string/menu_receive" />
</menu>

View File

@ -9,4 +9,11 @@
android:title="@string/menu_info"
app:showAsAction="always" />
<item
android:id="@+id/action_receive"
android:icon="@drawable/ic_monero_qr_24dp"
android:orderInCategory="100"
android:title="@string/menu_receive"
app:showAsAction="always" />
</menu>

View File

@ -1,9 +1,10 @@
<resources>
<string name="app_name">Monerujo</string>
<string name="login_activity_name">Select Wallet</string>
<string name="login_activity_name">Monerujo</string>
<string name="wallet_activity_name">Wallet</string>
<string name="menu_info">Details</string>
<string name="menu_receive">Receive</string>
<string name="prompt_daemon">[&lt;user&gt;:&lt;pass&gt;@]&lt;daemon&gt;[:&lt;port&gt;]</string>
<string name="prompt_mainnet">Net Selection</string>
@ -151,6 +152,13 @@
<string name="tx_pending">PENDING</string>
<string name="tx_failed">FAILED</string>
<string name="receive_generate_hint">Show me the QR Code</string>
<string name="receive_paymentid_button">Generate</string>
<string name="receive_paymentid_label">PaymentID</string>
<string name="receive_paymentid_hint">(optional)</string>
<string name="receive_amount_label">Amount</string>
<string name="receive_amount_hint">(optional)</string>
<string name="big_amount">999999.999999999999</string>
<string-array name="mixin">