diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index 420affc..2be7399 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -774,7 +774,8 @@ Java_com_m2049r_xmrwallet_model_Wallet_createTransactionJ(JNIEnv *env, jobject i } JNIEXPORT jlong JNICALL -Java_com_m2049r_xmrwallet_model_Wallet_createSweepUnmixableTransactionJ(JNIEnv *env, jobject instance) { +Java_com_m2049r_xmrwallet_model_Wallet_createSweepUnmixableTransactionJ(JNIEnv *env, + jobject instance) { Bitmonero::Wallet *wallet = getHandle(env, instance); Bitmonero::PendingTransaction *tx = wallet->createSweepUnmixableTransaction(); return reinterpret_cast(tx); @@ -839,12 +840,40 @@ Java_com_m2049r_xmrwallet_model_Wallet_setDefaultMixin(JNIEnv *env, jobject inst return wallet->setDefaultMixin(mixin); } -//virtual bool setUserNote(const std::string &txid, const std::string ¬e) = 0; -//virtual std::string getUserNote(const std::string &txid) const = 0; +JNIEXPORT jboolean JNICALL +Java_com_m2049r_xmrwallet_model_Wallet_setUserNote(JNIEnv *env, jobject instance, + jstring txid, jstring note) { + + const char *_txid = env->GetStringUTFChars(txid, JNI_FALSE); + const char *_note = env->GetStringUTFChars(note, JNI_FALSE); + + Bitmonero::Wallet *wallet = getHandle(env, instance); + + bool success = wallet->setUserNote(_txid, _note); + + env->ReleaseStringUTFChars(txid, _txid); + env->ReleaseStringUTFChars(note, _note); + + return success; +} + +JNIEXPORT jstring JNICALL +Java_com_m2049r_xmrwallet_model_Wallet_getUserNote(JNIEnv *env, jobject instance, + jstring txid) { + + const char *_txid = env->GetStringUTFChars(txid, JNI_FALSE); + + Bitmonero::Wallet *wallet = getHandle(env, instance); + + std::string note = wallet->getUserNote(_txid); + + env->ReleaseStringUTFChars(txid, _txid); + return env->NewStringUTF(note.c_str()); +} JNIEXPORT jstring JNICALL Java_com_m2049r_xmrwallet_model_Wallet_getTxKey(JNIEnv *env, jobject instance, - jstring txid) { + jstring txid) { const char *_txid = env->GetStringUTFChars(txid, JNI_FALSE); @@ -883,8 +912,10 @@ jobject newTransferInstance(JNIEnv *env, uint64_t amount, const std::string &add jobject newTransferList(JNIEnv *env, Bitmonero::TransactionInfo *info) { const std::vector &transfers = info->transfers(); + if (transfers.size()==0) { // don't create empty Lists + return nullptr; + } // make new ArrayList - jmethodID java_util_ArrayList_ = env->GetMethodID(class_ArrayList, "", "(I)V"); jmethodID java_util_ArrayList_add = env->GetMethodID(class_ArrayList, "add", "(Ljava/lang/Object;)Z"); @@ -932,8 +963,11 @@ jobject cpp2java(JNIEnv *env, std::vector vector) jobject arrayList = env->NewObject(class_ArrayList, java_util_ArrayList_, vector.size()); for (Bitmonero::TransactionInfo *s: vector) { - if (s->fee()>1) { - LOGE("TX %s %" PRIu64 " %" PRIu64, s->hash().c_str(), s->fee(), s->amount()); + if (s->fee() > 1) { + LOGE("TX %s %" + PRIu64 + " %" + PRIu64, s->hash().c_str(), s->fee(), s->amount()); } jobject info = newTransactionInfo(env, s); env->CallBooleanMethod(arrayList, java_util_ArrayList_add, info); @@ -995,13 +1029,20 @@ Java_com_m2049r_xmrwallet_model_PendingTransaction_getFee(JNIEnv *env, jobject i return tx->fee(); } -/* TODO this returns a vector of strings - deal with this later +// TODO this returns a vector of strings - deal with this later - for now return first one JNIEXPORT jstring JNICALL -Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxId(JNIEnv *env, jobject instance) { +Java_com_m2049r_xmrwallet_model_PendingTransaction_getFirstTxId(JNIEnv *env, jobject instance) { Bitmonero::PendingTransaction *tx = getHandle(env, instance); - return env->NewStringUTF(tx->txid().c_str()); + + std::vector txids = tx->txid(); + + for (std::string &s: txids) { + LOGD("TX %s", s.c_str()); + } + + return env->NewStringUTF(txids.front().c_str()); } -*/ + JNIEXPORT jlong JNICALL Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxCount(JNIEnv *env, jobject instance) { diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java index 99caab6..25a0424 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java @@ -27,7 +27,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; -import android.provider.MediaStore; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.util.Log; @@ -274,13 +273,13 @@ public class LoginActivity extends AppCompatActivity Log.d(TAG, "GenerateReviewFragment placed"); } - void replaceFragment(Fragment newFragment, String name, Bundle extras) { + void replaceFragment(Fragment newFragment, String stackName, Bundle extras) { if (extras != null) { newFragment.setArguments(extras); } FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.fragment_container, newFragment); - transaction.addToBackStack(name); + transaction.addToBackStack(stackName); transaction.commit(); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java b/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java index 7e5dc44..698512f 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/SendFragment.java @@ -17,8 +17,12 @@ 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.support.v7.app.AlertDialog; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; @@ -36,6 +40,8 @@ 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; @@ -56,6 +62,7 @@ public class SendFragment extends Fragment { TextView tvTxAmount; TextView tvTxFee; TextView tvTxDust; + EditText etNotes; Button bSend; ProgressBar pbProgress; @@ -84,12 +91,14 @@ public class SendFragment extends Fragment { tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount); tvTxFee = (TextView) view.findViewById(R.id.tvTxFee); tvTxDust = (TextView) view.findViewById(R.id.tvTxDust); + etNotes = (EditText) view.findViewById(R.id.etNotes); bSend = (Button) view.findViewById(R.id.bSend); pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress); etAddress.setRawInputType(InputType.TYPE_CLASS_TEXT); etPaymentId.setRawInputType(InputType.TYPE_CLASS_TEXT); + etNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); etAddress.setText("9tDC52GsMjTNt4dpnRCwAF7ekVBkbkgkXGaMKTcSTpBhGpqkPX56jCNRydLq9oGjbbAQBsZhLfgmTKsntmxRd3TaJFYM2f8"); boolean testnet = WalletManager.getInstance().isTestNet(); @@ -143,6 +152,8 @@ public class SendFragment extends Fragment { if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) { if (amountOk()) { Helper.hideKeyboard(getActivity()); + disableEdit(); + prepareSend(); } return true; } @@ -175,6 +186,7 @@ public class SendFragment extends Fragment { @Override public void onClick(View v) { Helper.hideKeyboard(getActivity()); + disableEdit(); prepareSend(); } }); @@ -196,11 +208,22 @@ public class SendFragment extends Fragment { } }); - bSend.setOnClickListener(new View.OnClickListener() + etNotes.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_DONE)) { + if (amountOk()) { + Helper.hideKeyboard(getActivity()); + } + return true; + } + return false; + } + }); - { + bSend.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + bSend.setEnabled(false); send(); } }); @@ -248,8 +271,6 @@ public class SendFragment extends Fragment { amount, mixin, priority); - - disableEdit(); showProgress(); activityCallback.onPrepareSend(txData); } @@ -286,8 +307,9 @@ public class SendFragment extends Fragment { } private void send() { - disableEdit(); // prevent this being sent more than once - activityCallback.onSend(); + etNotes.setEnabled(false); + String notes = etNotes.getText().toString(); + activityCallback.onSend(notes); } SendFragment.Listener activityCallback; @@ -297,7 +319,7 @@ public class SendFragment extends Fragment { void onPrepareSweep(); - void onSend(); + void onSend(String notes); String generatePaymentId(); @@ -331,6 +353,21 @@ public class SendFragment extends Fragment { bSend.setEnabled(true); } + public void onCreatedTransactionFailed(String errorText) { + hideProgress(); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(getString(R.string.send_error_title)); + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + enableEdit(); + } + }); + builder.setMessage(errorText); + builder.create().show(); + } + public void showProgress() { pbProgress.setIndeterminate(true); pbProgress.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java new file mode 100644 index 0000000..e9b74e6 --- /dev/null +++ b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java @@ -0,0 +1,236 @@ +/* + * 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.app.Fragment; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Bundle; +import android.text.InputType; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +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.service.MoneroHandlerThread; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +public class TxFragment extends Fragment { + static final String TAG = "TxFragment"; + + static public final String ARG_INFO = "info"; + + private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + public TxFragment() { + super(); + Calendar cal = Calendar.getInstance(); + TimeZone tz = cal.getTimeZone(); //get the local time zone. + TS_FORMATTER.setTimeZone(tz); + } + + TextView tvTxTimestamp; + TextView tvTxId; + TextView tvTxKey; + TextView tvTxPaymentId; + TextView tvTxBlockheight; + TextView tvTxAmount; + TextView tvTxFee; + TextView tvTxTransfers; + TextView etTxNotes; + Button bCopy; + Button bTxNotes; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View view = inflater.inflate(R.layout.tx_fragment, container, false); + + tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp); + tvTxId = (TextView) view.findViewById(R.id.tvTxId); + tvTxKey = (TextView) view.findViewById(R.id.tvTxKey); + tvTxPaymentId = (TextView) view.findViewById(R.id.tvTxPaymentId); + tvTxBlockheight = (TextView) view.findViewById(R.id.tvTxBlockheight); + tvTxAmount = (TextView) view.findViewById(R.id.tvTxAmount); + tvTxFee = (TextView) view.findViewById(R.id.tvTxFee); + tvTxTransfers = (TextView) view.findViewById(R.id.tvTxTransfers); + etTxNotes = (TextView) view.findViewById(R.id.etTxNotes); + bCopy = (Button) view.findViewById(R.id.bCopy); + bTxNotes = (Button) view.findViewById(R.id.bTxNotes); + + etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT); + + bCopy.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + copyToClipboard(); + } + }); + + bTxNotes.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + info.notes = null; // force reload on next view + bTxNotes.setEnabled(false); + etTxNotes.setEnabled(false); + activityCallback.onSetNote(info.hash, etTxNotes.getText().toString()); + } + }); + + Bundle args = getArguments(); + TransactionInfo info = args.getParcelable(ARG_INFO); + show(info); + return view; + } + + public void onNotesSet(boolean reload) { + bTxNotes.setEnabled(true); + etTxNotes.setEnabled(true); + if (reload) { + loadNotes(this.info); + } + } + + void copyToClipboard() { + if (this.info == null) return; + StringBuffer sb = new StringBuffer(); + sb.append(getString(R.string.tx_address)).append(": "); + sb.append(activityCallback.getWalletAddress()).append("\n"); + sb.append(getString(R.string.tx_id)).append(": "); + sb.append(info.hash).append("\n"); + sb.append(getString(R.string.tx_key)).append(": "); + sb.append(info.txKey.isEmpty() ? "-" : info.txKey).append("\n"); + sb.append(getString(R.string.tx_paymentId)).append(": "); + sb.append(info.paymentId).append("\n"); + sb.append(getString(R.string.tx_amount)).append(": "); + sb.append(Wallet.getDisplayAmount(info.amount)).append("\n"); + sb.append(getString(R.string.tx_fee)).append(": "); + sb.append(Wallet.getDisplayAmount(info.fee)).append("\n"); + sb.append(getString(R.string.tx_notes)).append(": "); + String oneLineNotes = info.notes.replace("\n", " ; "); + sb.append(oneLineNotes.isEmpty() ? "-" : oneLineNotes).append("\n"); + 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(": "); + sb.append(info.blockheight).append("\n"); + sb.append(getString(R.string.tx_transfers)).append(": "); + if (info.transfers != null) { + boolean comma = false; + for (int i = 0; i < 10; i++) + for (Transfer transfer : info.transfers) { + if (comma) { + sb.append(","); + } else { + comma = true; + } + sb.append("[").append(transfer.address.substring(0, 6)).append("] "); + sb.append(Wallet.getDisplayAmount(transfer.amount)); + } + } else { + sb.append("-"); + } + sb.append("\n"); + ClipboardManager clipboardManager = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(getString(R.string.tx_copy_label), sb.toString()); + clipboardManager.setPrimaryClip(clip); + Toast.makeText(getActivity(), getString(R.string.tx_copy_message), Toast.LENGTH_SHORT).show(); + Log.d(TAG, sb.toString()); + } + + TransactionInfo info = null; + + void loadNotes(TransactionInfo info) { + if (info.notes == null) { + info.notes = activityCallback.getTxNotes(info.hash); + //Log.d(TAG, "NOTES:" + info.notes + ":"); + } + etTxNotes.setText(info.notes); + } + + private void show(TransactionInfo info) { + if (info.txKey == null) { + info.txKey = activityCallback.getTxKey(info.hash); + //Log.d(TAG, "TXKEY:" + info.txKey + ":"); + } + loadNotes(info); + tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000))); + tvTxId.setText(info.hash); + tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey); + tvTxPaymentId.setText(info.paymentId); + tvTxBlockheight.setText("" + info.blockheight); + tvTxAmount.setText(Wallet.getDisplayAmount(info.amount)); + tvTxFee.setText(Wallet.getDisplayAmount(info.fee)); + StringBuffer sb = new StringBuffer(); + if (info.transfers != null) { + boolean newline = false; + for (int i = 0; i < 10; i++) + for (Transfer transfer : info.transfers) { + if (newline) { + sb.append("\n"); + } else { + newline = true; + } + sb.append("[").append(transfer.address.substring(0, 6)).append("] "); + sb.append(Wallet.getDisplayAmount(transfer.amount)); + } + } else { + sb.append("-"); + } + tvTxTransfers.setText(sb.toString()); + this.info = info; + bCopy.setEnabled(true); + } + + TxFragment.Listener activityCallback; + + public interface Listener { + String getWalletAddress(); + + String getTxKey(String hash); + + String getTxNotes(String hash); + + void onSetNote(String txId, String notes); + + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof TxFragment.Listener) { + this.activityCallback = (TxFragment.Listener) context; + } else { + throw new ClassCastException(context.toString() + + " must implement Listener"); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index eb18d89..7747d03 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -32,12 +32,13 @@ import android.util.Log; import android.widget.Toast; import com.m2049r.xmrwallet.model.PendingTransaction; +import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.util.TxData; public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener, - WalletService.Observer, SendFragment.Listener { + WalletService.Observer, SendFragment.Listener, TxFragment.Listener { private static final String TAG = "WalletActivity"; static final int MIN_DAEMON_VERSION = 65544; @@ -62,6 +63,11 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. return getWallet().getTxKey(txId); } + @Override + public String getTxNotes(String txId) { + return getWallet().getUserNote(txId); + } + @Override protected void onStart() { super.onStart(); @@ -258,6 +264,13 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. replaceFragment(new SendFragment(), null, null); } + @Override + public void onTxDetailsRequest(TransactionInfo info) { + Bundle args = new Bundle(); + args.putParcelable(TxFragment.ARG_INFO, info); + replaceFragment(new TxFragment(), null, args); + } + @Override public void forceUpdate() { try { @@ -299,6 +312,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. return true; } catch (ClassCastException ex) { // not in wallet fragment (probably send monero) + Log.d(TAG, ex.getLocalizedMessage()); // keep calm and carry on } return false; @@ -319,18 +333,16 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. @Override public void onCreatedTransaction(final PendingTransaction pendingTransaction) { - final PendingTransaction.Status status = pendingTransaction.getStatus(); - if (status != PendingTransaction.Status.Status_Ok) { - getWallet().disposePendingTransaction(); - } try { final SendFragment sendFragment = (SendFragment) getFragmentManager().findFragmentById(R.id.fragment_container); runOnUiThread(new Runnable() { public void run() { + PendingTransaction.Status status = pendingTransaction.getStatus(); if (status != PendingTransaction.Status.Status_Ok) { - Toast.makeText(WalletActivity.this, getString(R.string.status_transaction_prepare_failed), Toast.LENGTH_LONG).show(); - sendFragment.onCreatedTransaction(null); + String errorText = pendingTransaction.getErrorString(); + getWallet().disposePendingTransaction(); + sendFragment.onCreatedTransactionFailed(errorText); } else { sendFragment.onCreatedTransaction(pendingTransaction); } @@ -338,6 +350,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. }); } catch (ClassCastException ex) { // not in spend fragment + Log.d(TAG, ex.getLocalizedMessage()); // don't need the transaction any more getWallet().disposePendingTransaction(); } @@ -357,6 +370,26 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. }); } + @Override + public void onSetNotes(final boolean success) { + try { + final TxFragment txFragment = (TxFragment) + getFragmentManager().findFragmentById(R.id.fragment_container); + runOnUiThread(new Runnable() { + public void run() { + if (!success) { + Toast.makeText(WalletActivity.this, getString(R.string.tx_notes_set_failed), Toast.LENGTH_LONG).show(); + } + txFragment.onNotesSet(success); + } + }); + } catch (ClassCastException ex) { + // not in tx fragment + Log.d(TAG, ex.getLocalizedMessage()); + // never min + } + } + @Override public void onProgress(final String text) { try { @@ -369,6 +402,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. }); } catch (ClassCastException ex) { // not in wallet fragment (probably send monero) + Log.d(TAG, ex.getLocalizedMessage()); // keep calm and carry on } } @@ -385,6 +419,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. }); } catch (ClassCastException ex) { // not in wallet fragment (probably send monero) + Log.d(TAG, ex.getLocalizedMessage()); // keep calm and carry on } } @@ -402,10 +437,11 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. /////////////////////////// @Override - public void onSend() { + public void onSend(String notes) { if (mIsBound) { // no point in talking to unbound service Intent intent = new Intent(getApplicationContext(), WalletService.class); intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SEND); + intent.putExtra(WalletService.REQUEST_CMD_SEND_NOTES, notes); startService(intent); Log.d(TAG, "SEND TX request sent"); } else { @@ -414,6 +450,21 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. } + @Override + public void onSetNote(String txId, String notes) { + if (mIsBound) { // no point in talking to unbound service + Intent intent = new Intent(getApplicationContext(), WalletService.class); + intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_SETNOTE); + intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_TX, txId); + intent.putExtra(WalletService.REQUEST_CMD_SETNOTE_NOTES, notes); + startService(intent); + Log.d(TAG, "SET NOTE request sent"); + } else { + Log.e(TAG, "Service not bound"); + } + + } + @Override public void onPrepareSend(TxData txData) { if (mIsBound) { // no point in talking to unbound service @@ -462,13 +513,13 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment. } } - void replaceFragment(Fragment newFragment, String name, Bundle extras) { + void replaceFragment(Fragment newFragment, String stackName, Bundle extras) { if (extras != null) { newFragment.setArguments(extras); } FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.fragment_container, newFragment); - transaction.addToBackStack(name); + transaction.addToBackStack(stackName); transaction.commit(); } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java index 914f013..fafdf30 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java @@ -102,61 +102,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O // Callbacks from TransactionInfoAdapter @Override public void onInteraction(final View view, final TransactionInfo infoItem) { - final Context ctx = view.getContext(); - AlertDialog.Builder builder = new AlertDialog.Builder(ctx); - builder.setTitle("Transaction details"); - - if (infoItem.txKey == null) { - infoItem.txKey = activityCallback.getTxKey(infoItem.hash); - } - - builder.setPositiveButton("Copy TX ID", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ClipboardManager clipboardManager = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("TXID", infoItem.hash); - clipboardManager.setPrimaryClip(clip); - } - }); - - if (!infoItem.txKey.isEmpty()) { - builder.setNegativeButton("Copy TX Key", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - ClipboardManager clipboardManager = (ClipboardManager) ctx.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("TXKEY", infoItem.txKey); - clipboardManager.setPrimaryClip(clip); - } - }); - } - - // TODO use strings.xml - StringBuffer sb = new StringBuffer(); - sb.append("TX ID: ").append(infoItem.hash); - sb.append("\nTX Key: "); - if (!infoItem.txKey.isEmpty()) { - sb.append(infoItem.txKey); - } else { - sb.append(" -"); - } - sb.append("\nPayment ID: ").append(infoItem.paymentId); - sb.append("\nBlockHeight: ").append(infoItem.blockheight); - sb.append("\nAmount: "); - sb.append(infoItem.direction == TransactionInfo.Direction.Direction_In ? "+" : "-"); - sb.append(Wallet.getDisplayAmount(infoItem.amount)); - sb.append("\nFee: ").append(Wallet.getDisplayAmount(infoItem.fee)); - sb.append("\nTransfers:"); - if (infoItem.transfers.size() > 0) { - for (Transfer transfer : infoItem.transfers) { - sb.append("\n[").append(transfer.address.substring(0, 6)).append("] "); - sb.append(Wallet.getDisplayAmount(transfer.amount)); - } - } else { - sb.append(" -"); - } - builder.setMessage(sb.toString()); - AlertDialog alert1 = builder.create(); - alert1.show(); + activityCallback.onTxDetailsRequest(infoItem); } // called from activity @@ -211,7 +157,9 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O if (shortName.length() > 16) { shortName = shortName.substring(0, 14) + "..."; } - String title = (wallet.isWatchOnly() ? "X " : "") + "[" + wallet.getAddress().substring(0, 6) + "] " + shortName; + String title = "[" + wallet.getAddress().substring(0, 6) + "] " + + shortName + + (wallet.isWatchOnly() ? " " + getString(R.string.watchonly_label) : ""); activityCallback.setTitle(title); Log.d(TAG, "wallet title is " + title); return title; @@ -269,6 +217,8 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O void onSendRequest(); + void onTxDetailsRequest(TransactionInfo info); + boolean isSynced(); boolean isWatchOnly(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java index 57ef2f0..e8e6028 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java +++ b/app/src/main/java/com/m2049r/xmrwallet/layout/TransactionInfoAdapter.java @@ -40,8 +40,8 @@ import java.util.TimeZone; public class TransactionInfoAdapter extends RecyclerView.Adapter { private static final String TAG = "TransactionInfoAdapter"; - private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd"); - private static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss"); + private final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd"); + private final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss"); static final int TX_RED = Color.rgb(255, 79, 65); static final int TX_GREEN = Color.rgb(54, 176, 91); diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/PendingTransaction.java b/app/src/main/java/com/m2049r/xmrwallet/model/PendingTransaction.java index 18ce09b..0c3e1ea 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/PendingTransaction.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/PendingTransaction.java @@ -39,16 +39,6 @@ public class PendingTransaction { Priority_High(3), Priority_Last(4); - private int value; - - Priority(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - public static Priority fromInteger(int n) { switch (n) { case 1: @@ -60,6 +50,18 @@ public class PendingTransaction { } return null; } + + public int getValue() { + return value; + } + + private int value; + + Priority(int value) { + this.value = value; + } + + } public Status getStatus() { @@ -79,7 +81,7 @@ public class PendingTransaction { public native long getFee(); - //public native String getTxId(); + public native String getFirstTxId(); public native long getTxCount(); diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java index d7c4331..92cef2c 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java @@ -16,16 +16,40 @@ package com.m2049r.xmrwallet.model; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + import java.util.List; // this is not the TransactionInfo from the API as that is owned by the TransactionHistory // this is a POJO for the TransactionInfoAdapter -public class TransactionInfo { +public class TransactionInfo implements Parcelable { static final String TAG = "TransactionInfo"; public enum Direction { - Direction_In, - Direction_Out + Direction_In(0), + Direction_Out(1); + + public static Direction fromInteger(int n) { + switch (n) { + case 0: + return Direction_In; + case 1: + return Direction_Out; + } + return null; + } + + public int getValue() { + return value; + } + + private int value; + + Direction(int value) { + this.value = value; + } } public Direction direction; @@ -41,6 +65,7 @@ public class TransactionInfo { public List transfers; public String txKey = null; + public String notes = null; public TransactionInfo( int direction, @@ -71,4 +96,52 @@ public class TransactionInfo { return direction + "@" + blockheight + " " + amount; } + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(direction.getValue()); + out.writeByte((byte) (isPending ? 1 : 0)); + out.writeByte((byte) (isFailed ? 1 : 0)); + out.writeLong(amount); + out.writeLong(fee); + out.writeLong(blockheight); + out.writeString(hash); + out.writeLong(timestamp); + out.writeString(paymentId); + out.writeLong(confirmations); + out.writeList(transfers); + out.writeString(txKey); + out.writeString(notes); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public TransactionInfo createFromParcel(Parcel in) { + return new TransactionInfo(in); + } + + public TransactionInfo[] newArray(int size) { + return new TransactionInfo[size]; + } + }; + + private TransactionInfo(Parcel in) { + direction = Direction.fromInteger(in.readInt()); + isPending = in.readByte() != 0; + isFailed = in.readByte() != 0; + amount = in.readLong(); + fee = in.readLong(); + blockheight = in.readLong(); + hash = in.readString(); + timestamp = in.readLong(); + paymentId = in.readString(); + confirmations = in.readLong(); + transfers = in.readArrayList(Transfer.class.getClassLoader()); + txKey = in.readString(); + notes = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/Transfer.java b/app/src/main/java/com/m2049r/xmrwallet/model/Transfer.java index cfcf350..0d9c0d0 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/Transfer.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/Transfer.java @@ -16,9 +16,12 @@ package com.m2049r.xmrwallet.model; -import android.util.Log; +import android.os.Parcel; +import android.os.Parcelable; -public class Transfer { +import com.m2049r.xmrwallet.util.TxData; + +public class Transfer implements Parcelable { public long amount; public String address; @@ -26,4 +29,31 @@ public class Transfer { this.amount = amount; this.address = address; } -} + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(amount); + out.writeString(address); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Transfer createFromParcel(Parcel in) { + return new Transfer(in); + } + + public Transfer[] newArray(int size) { + return new Transfer[size]; + } + }; + + private Transfer(Parcel in) { + amount = in.readLong(); + address = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java index e96c27c..e5192ef 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java @@ -233,8 +233,9 @@ public class Wallet { public native void setDefaultMixin(int mixin); - //virtual bool setUserNote(const std::string &txid, const std::string ¬e) = 0; -//virtual std::string getUserNote(const std::string &txid) const = 0; + public native boolean setUserNote(String txid, String note); + public native String getUserNote(String txid); + public native String getTxKey(String txid); //virtual std::string signMessage(const std::string &message) = 0; diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java index 02e201b..4dd9d61 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java +++ b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java @@ -53,6 +53,11 @@ public class WalletService extends Service { public static final String REQUEST_CMD_SWEEP = "sweepTX"; public static final String REQUEST_CMD_SEND = "send"; + public static final String REQUEST_CMD_SEND_NOTES = "notes"; + + public static final String REQUEST_CMD_SETNOTE = "setnote"; + public static final String REQUEST_CMD_SETNOTE_TX = "tx"; + public static final String REQUEST_CMD_SETNOTE_NOTES = "notes"; public static final int START_SERVICE = 1; public static final int STOP_SERVICE = 2; @@ -211,6 +216,8 @@ public class WalletService extends Service { void onCreatedTransaction(PendingTransaction pendingTransaction); void onSentTransaction(boolean success); + + void onSetNotes(boolean success); } String progressText = null; @@ -320,11 +327,15 @@ public class WalletService extends Service { myWallet.disposePendingTransaction(); // it's broken anyway return; } + String txid = pendingTransaction.getFirstTxId(); boolean success = pendingTransaction.commit("", true); myWallet.disposePendingTransaction(); if (observer != null) observer.onSentTransaction(success); - if (success) { + String notes = extras.getString(REQUEST_CMD_SEND_NOTES); + if ((notes != null) && (!notes.isEmpty())) { + myWallet.setUserNote(txid, notes); + } boolean rc = myWallet.store(); Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc); if (!rc) { @@ -332,6 +343,26 @@ public class WalletService extends Service { } if (observer != null) observer.onWalletStored(rc); } + } else if (cmd.equals(REQUEST_CMD_SETNOTE)) { + Wallet myWallet = getWallet(); + Log.d(TAG, "SET NOTE for wallet: " + myWallet.getName()); + String txId = extras.getString(REQUEST_CMD_SETNOTE_TX); + String notes = extras.getString(REQUEST_CMD_SETNOTE_NOTES); + if ((txId != null) && (notes != null)) { + boolean success = myWallet.setUserNote(txId, notes); + if (!success) { + Log.e(TAG, myWallet.getErrorString()); + } + if (observer != null) observer.onSetNotes(success); + if (success) { + boolean rc = myWallet.store(); + Log.d(TAG, "wallet stored: " + myWallet.getName() + " with rc=" + rc); + if (!rc) { + Log.d(TAG, "Wallet store failed: " + myWallet.getErrorString()); + } + if (observer != null) observer.onWalletStored(rc); + } + } } } break; diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/TxData.java b/app/src/main/java/com/m2049r/xmrwallet/util/TxData.java index 453b747..13b3c4d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/util/TxData.java +++ b/app/src/main/java/com/m2049r/xmrwallet/util/TxData.java @@ -24,10 +24,10 @@ import com.m2049r.xmrwallet.model.PendingTransaction; // https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents public class TxData implements Parcelable { public TxData(String dst_addr, - String paymentId, - long amount, - int mixin, - PendingTransaction.Priority priority) { + String paymentId, + long amount, + int mixin, + PendingTransaction.Priority priority) { this.dst_addr = dst_addr; this.paymentId = paymentId; this.amount = amount; @@ -41,13 +41,6 @@ public class TxData implements Parcelable { public int mixin; public PendingTransaction.Priority priority; - // 99.9% of the time you can just ignore this - @Override - public int describeContents() { - return 0; - } - - // write your object's data to the passed-in Parcel @Override public void writeToParcel(Parcel out, int flags) { out.writeString(dst_addr); @@ -68,7 +61,6 @@ public class TxData implements Parcelable { } }; - // example constructor that takes a Parcel and gives you an object populated with it's values private TxData(Parcel in) { dst_addr = in.readString(); paymentId = in.readString(); @@ -77,4 +69,10 @@ public class TxData implements Parcelable { priority = PendingTransaction.Priority.fromInteger(in.readInt()); } + + @Override + public int describeContents() { + return 0; + } + } diff --git a/app/src/main/res/layout/gen_fragment.xml b/app/src/main/res/layout/gen_fragment.xml index 3b51885..0619d41 100644 --- a/app/src/main/res/layout/gen_fragment.xml +++ b/app/src/main/res/layout/gen_fragment.xml @@ -5,7 +5,7 @@ android:orientation="vertical"> diff --git a/app/src/main/res/layout/gen_review_fragment.xml b/app/src/main/res/layout/gen_review_fragment.xml index 6d0a55e..56a2a72 100644 --- a/app/src/main/res/layout/gen_review_fragment.xml +++ b/app/src/main/res/layout/gen_review_fragment.xml @@ -5,7 +5,7 @@ android:orientation="vertical"> @@ -32,7 +32,7 @@ @@ -67,7 +67,7 @@ @@ -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" />