From af0ecb2894eb7cc1b26b978f7b1b8923dd917f1a Mon Sep 17 00:00:00 2001 From: m2049r Date: Thu, 14 Jun 2018 21:32:52 +0200 Subject: [PATCH] accounts (#312) --- app/src/main/cpp/monerujo.cpp | 87 ++++++-- .../java/com/m2049r/xmrwallet/TxFragment.java | 4 + .../com/m2049r/xmrwallet/WalletActivity.java | 186 +++++++++++++++++- .../com/m2049r/xmrwallet/WalletFragment.java | 30 ++- .../xmrwallet/model/TransactionHistory.java | 34 +++- .../xmrwallet/model/TransactionInfo.java | 10 + .../com/m2049r/xmrwallet/model/Wallet.java | 90 +++++++-- .../m2049r/xmrwallet/model/WalletManager.java | 7 + .../ic_account_balance_wallet_black_24dp.xml | 5 + .../main/res/drawable/ic_add_circle_white.xml | 9 + .../main/res/drawable/ic_edit_white_24dp.xml | 5 + app/src/main/res/layout/activity_wallet.xml | 49 +++-- app/src/main/res/layout/fragment_tx_info.xml | 18 ++ app/src/main/res/layout/nav_header.xml | 28 +++ app/src/main/res/menu/drawer_view.xml | 16 ++ app/src/main/res/menu/wallet_menu.xml | 7 + app/src/main/res/values-de/strings.xml | 8 + app/src/main/res/values-es/strings.xml | 8 + app/src/main/res/values-fr/strings.xml | 8 + app/src/main/res/values-it/strings.xml | 8 + app/src/main/res/values-nb/strings.xml | 8 + app/src/main/res/values-zh-rCN/strings.xml | 8 + app/src/main/res/values-zh-rTW/strings.xml | 8 + app/src/main/res/values/strings.xml | 10 + app/src/main/res/values/styles.xml | 4 + 25 files changed, 606 insertions(+), 49 deletions(-) create mode 100644 app/src/main/res/drawable/ic_account_balance_wallet_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_add_circle_white.xml create mode 100644 app/src/main/res/drawable/ic_edit_white_24dp.xml create mode 100644 app/src/main/res/layout/nav_header.xml create mode 100644 app/src/main/res/menu/drawer_view.xml diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index ee0e5e1..1470cd3 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -551,9 +551,10 @@ Java_com_m2049r_xmrwallet_model_Wallet_setPassword(JNIEnv *env, jobject instance } JNIEXPORT jstring JNICALL -Java_com_m2049r_xmrwallet_model_Wallet_getAddressJ(JNIEnv *env, jobject instance) { +Java_com_m2049r_xmrwallet_model_Wallet_getAddressJ(JNIEnv *env, jobject instance, + jint accountIndex) { Bitmonero::Wallet *wallet = getHandle(env, instance); - return env->NewStringUTF(wallet->address().c_str()); + return env->NewStringUTF(wallet->address((uint32_t) accountIndex).c_str()); } JNIEXPORT jstring JNICALL @@ -646,15 +647,29 @@ Java_com_m2049r_xmrwallet_model_Wallet_getConnectionStatusJ(JNIEnv *env, jobject //TODO virtual bool trustedDaemon() const = 0; JNIEXPORT jlong JNICALL -Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance) { +Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance, + jint accountIndex) { Bitmonero::Wallet *wallet = getHandle(env, instance); - return wallet->balance(); + return wallet->balance((uint32_t) accountIndex); } JNIEXPORT jlong JNICALL -Java_com_m2049r_xmrwallet_model_Wallet_getUnlockedBalance(JNIEnv *env, jobject instance) { +Java_com_m2049r_xmrwallet_model_Wallet_getBalanceAll(JNIEnv *env, jobject instance) { Bitmonero::Wallet *wallet = getHandle(env, instance); - return wallet->unlockedBalance(); + return wallet->balanceAll(); +} + +JNIEXPORT jlong JNICALL +Java_com_m2049r_xmrwallet_model_Wallet_getUnlockedBalance(JNIEnv *env, jobject instance, + jint accountIndex) { + Bitmonero::Wallet *wallet = getHandle(env, instance); + return wallet->unlockedBalance((uint32_t) accountIndex); +} + +JNIEXPORT jlong JNICALL +Java_com_m2049r_xmrwallet_model_Wallet_getUnlockedBalanceAll(JNIEnv *env, jobject instance) { + Bitmonero::Wallet *wallet = getHandle(env, instance); + return wallet->unlockedBalanceAll(); } JNIEXPORT jboolean JNICALL @@ -814,7 +829,8 @@ JNIEXPORT jlong JNICALL Java_com_m2049r_xmrwallet_model_Wallet_createTransactionJ(JNIEnv *env, jobject instance, jstring dst_addr, jstring payment_id, jlong amount, jint mixin_count, - jint priority) { + jint priority, + jint accountIndex) { const char *_dst_addr = env->GetStringUTFChars(dst_addr, NULL); const char *_payment_id = env->GetStringUTFChars(payment_id, NULL); @@ -823,8 +839,9 @@ Java_com_m2049r_xmrwallet_model_Wallet_createTransactionJ(JNIEnv *env, jobject i Bitmonero::Wallet *wallet = getHandle(env, instance); Bitmonero::PendingTransaction *tx = wallet->createTransaction(_dst_addr, _payment_id, - amount, mixin_count, - _priority); + amount, (uint32_t) mixin_count, + _priority, + (uint32_t) accountIndex); env->ReleaseStringUTFChars(dst_addr, _dst_addr); env->ReleaseStringUTFChars(payment_id, _payment_id); @@ -943,6 +960,54 @@ Java_com_m2049r_xmrwallet_model_Wallet_getTxKey(JNIEnv *env, jobject instance, return env->NewStringUTF(txKey.c_str()); } +//virtual void addSubaddressAccount(const std::string& label) = 0; +JNIEXPORT void JNICALL +Java_com_m2049r_xmrwallet_model_Wallet_addAccount(JNIEnv *env, jobject instance, + jstring label) { + + const char *_label = env->GetStringUTFChars(label, NULL); + + Bitmonero::Wallet *wallet = getHandle(env, instance); + wallet->addSubaddressAccount(_label); + + env->ReleaseStringUTFChars(label, _label); +} + +//virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const = 0; +JNIEXPORT jstring JNICALL +Java_com_m2049r_xmrwallet_model_Wallet_getSubaddressLabel(JNIEnv *env, jobject instance, + jint accountIndex, jint addressIndex) { + + Bitmonero::Wallet *wallet = getHandle(env, instance); + + std::string label = wallet->getSubaddressLabel((uint32_t) accountIndex, + (uint32_t) addressIndex); + + return env->NewStringUTF(label.c_str()); +} + +//virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; +JNIEXPORT void JNICALL +Java_com_m2049r_xmrwallet_model_Wallet_setSubaddressLabel(JNIEnv *env, jobject instance, + jint accountIndex, jint addressIndex, + jstring label) { + + const char *_label = env->GetStringUTFChars(label, NULL); + + Bitmonero::Wallet *wallet = getHandle(env, instance); + wallet->setSubaddressLabel(accountIndex, addressIndex, _label); + + env->ReleaseStringUTFChars(label, _label); +} + +// virtual size_t numSubaddressAccounts() const = 0; +JNIEXPORT jint JNICALL +Java_com_m2049r_xmrwallet_model_Wallet_numSubaddressAccounts(JNIEnv *env, jobject instance) { + Bitmonero::Wallet *wallet = getHandle(env, instance); + return wallet->numSubaddressAccounts(); +} + + //virtual std::string signMessage(const std::string &message) = 0; //virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; @@ -989,7 +1054,7 @@ jobject newTransferList(JNIEnv *env, Bitmonero::TransactionInfo *info) { jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) { jmethodID c = env->GetMethodID(class_TransactionInfo, "", - "(IZZJJJLjava/lang/String;JLjava/lang/String;JLjava/util/List;)V"); + "(IZZJJJLjava/lang/String;JLjava/lang/String;IJLjava/util/List;)V"); jobject transfers = newTransferList(env, info); jstring _hash = env->NewStringUTF(info->hash().c_str()); jstring _paymentId = env->NewStringUTF(info->paymentId().c_str()); @@ -1003,6 +1068,7 @@ jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) { _hash, static_cast (info->timestamp()), _paymentId, + info->subaddrAccount(), info->confirmations(), transfers); env->DeleteLocalRef(transfers); @@ -1178,7 +1244,6 @@ Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject i Bitmonero::WalletManagerFactory::setLogLevel(level); } - #ifdef __cplusplus } #endif diff --git a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java index 1d21e5b..5cc7aa9 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/TxFragment.java @@ -59,6 +59,7 @@ public class TxFragment extends Fragment { TS_FORMATTER.setTimeZone(tz); } + private TextView tvAccount; private TextView tvTxTimestamp; private TextView tvTxId; private TextView tvTxKey; @@ -88,6 +89,7 @@ public class TxFragment extends Fragment { tvDestinationBtc = (TextView) view.findViewById(R.id.tvDestinationBtc); tvTxAmountBtc = (TextView) view.findViewById(R.id.tvTxAmountBtc); + tvAccount = (TextView) view.findViewById(R.id.tvAccount); tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp); tvTxId = (TextView) view.findViewById(R.id.tvTxId); tvTxKey = (TextView) view.findViewById(R.id.tvTxKey); @@ -222,6 +224,8 @@ public class TxFragment extends Fragment { activityCallback.setSubtitle(getString(R.string.tx_title)); activityCallback.setToolbarButton(Toolbar.BUTTON_BACK); + tvAccount.setText("" + info.subaddrAccount); + tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000))); tvTxId.setText(info.hash); tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey); diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java index 21bea51..f457563 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java @@ -28,10 +28,21 @@ import android.os.Bundle; import android.os.IBinder; import android.os.PowerManager; import android.support.annotation.NonNull; +import android.support.design.widget.NavigationView; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; import com.m2049r.xmrwallet.data.BarcodeData; @@ -49,6 +60,9 @@ import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.UserNotes; import com.m2049r.xmrwallet.widget.Toolbar; +import java.util.ArrayList; +import java.util.List; + import timber.log.Timber; public class WalletActivity extends SecureActivity implements WalletFragment.Listener, @@ -57,12 +71,18 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis GenerateReviewFragment.Listener, GenerateReviewFragment.PasswordChangedListener, ScannerFragment.OnScannedListener, ReceiveFragment.Listener, - SendAddressWizardFragment.OnScanListener { + SendAddressWizardFragment.OnScanListener, + WalletFragment.DrawerLocker, + NavigationView.OnNavigationItemSelectedListener { public static final String REQUEST_ID = "id"; public static final String REQUEST_PW = "pw"; public static final String REQUEST_FINGERPRINT_USED = "fingerprint"; + private NavigationView accountsView; + private DrawerLayout drawer; + private ActionBarDrawerToggle drawerToggle; + private Toolbar toolbar; private boolean needVerifyIdentity; @@ -155,10 +175,11 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis @Override protected void onDestroy() { Timber.d("onDestroy()"); - if ((mBoundService != null) && !isSynced() && (getWallet() != null)) { + if ((mBoundService != null) && (getWallet() != null)) { saveWallet(); } stopWalletService(); + drawer.removeDrawerListener(drawerToggle); super.onDestroy(); } @@ -167,6 +188,14 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis return haveWallet; } + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem renameItem = menu.findItem(R.id.action_rename); + if (renameItem != null) + renameItem.setVisible(hasWallet() && getWallet().isSynchronized()); + return true; + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -194,6 +223,9 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis case R.id.action_help_send: HelpFragment.display(getSupportFragmentManager(), R.string.help_send); return true; + case R.id.action_rename: + onAccountRename(); + return true; default: return super.onOptionsItemSelected(item); } @@ -253,6 +285,16 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis } }); + drawer = (DrawerLayout) findViewById(R.id.drawer_layout); + drawerToggle = new ActionBarDrawerToggle(this, drawer, toolbar, + R.string.accounts_drawer_open, R.string.accounts_drawer_close); + drawer.addDrawerListener(drawerToggle); + drawerToggle.syncState(); + setDrawerEnabled(false); // disable until synced + + accountsView = (NavigationView) findViewById(R.id.accounts_nav); + accountsView.setNavigationItemSelectedListener(this); + showNet(); Fragment walletFragment = new WalletFragment(); @@ -280,7 +322,6 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis } } - public Wallet getWallet() { if (mBoundService == null) throw new IllegalStateException("WalletService not bound."); return mBoundService.getWallet(); @@ -444,10 +485,20 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis // WalletService.Observer /////////////////////////// + private int numAccounts = -1; + // refresh and return true if successful @Override public boolean onRefreshed(final Wallet wallet, final boolean full) { Timber.d("onRefreshed()"); + if (numAccounts != wallet.numAccounts()) { + numAccounts = wallet.numAccounts(); + runOnUiThread(new Runnable() { + public void run() { + updateAccountsList(); + } + }); + } try { final WalletFragment walletFragment = (WalletFragment) getSupportFragmentManager().findFragmentByTag(WalletFragment.class.getName()); @@ -869,6 +920,11 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis @Override public void onBackPressed() { + if (drawer.isDrawerOpen(GravityCompat.START)) { + drawer.closeDrawer(GravityCompat.START); + return; + } + final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); if (fragment instanceof OnBackPressedListener) { if (!((OnBackPressedListener) fragment).onBackPressed()) { @@ -889,4 +945,128 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis return getPreferences(Context.MODE_PRIVATE); } + private List accountIds = new ArrayList<>(); + + // generate and cache unique ids for use in accounts list + private int getAccountId(int accountIndex) { + final int n = accountIds.size(); + for (int i = n; i <= accountIndex; i++) { + accountIds.add(View.generateViewId()); + } + return accountIds.get(accountIndex); + } + + // drawer stuff + void updateAccountsList() { + final Wallet wallet = getWallet(); + final TextView tvName = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvName); + tvName.setText(wallet.getName()); + final TextView tvBalance = (TextView) accountsView.getHeaderView(0).findViewById(R.id.tvBalance); + tvBalance.setText(getString(R.string.accounts_balance, + Helper.getDisplayAmount(wallet.getBalanceAll() + wallet.getUnlockedBalanceAll(), + 5))); + Menu menu = accountsView.getMenu(); + menu.removeGroup(R.id.accounts_list); + for (int i = 0; i < wallet.numAccounts(); i++) { + final String label = wallet.getAccountLabel(i); + final MenuItem item = menu.add(R.id.accounts_list, getAccountId(i), 2 * i, label); + item.setIcon(R.drawable.ic_account_balance_wallet_black_24dp); + if (i == wallet.getAccountIndex()) + item.setChecked(true); + } + menu.setGroupCheckable(R.id.accounts_list, true, true); + } + + @Override + public void setDrawerEnabled(boolean enabled) { + Timber.d("setDrawerEnabled %b", enabled); + final int lockMode = enabled ? DrawerLayout.LOCK_MODE_UNLOCKED : + DrawerLayout.LOCK_MODE_LOCKED_CLOSED; + drawer.setDrawerLockMode(lockMode); + drawerToggle.setDrawerIndicatorEnabled(enabled); + invalidateOptionsMenu(); // menu may need to be changed + } + + void updateAccountName() { + setSubtitle(getWallet().getAccountLabel()); + updateAccountsList(); + } + + public void onAccountRename() { + final LayoutInflater li = LayoutInflater.from(this); + final View promptsView = li.inflate(R.layout.prompt_rename, null); + + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setView(promptsView); + + final EditText etRename = (EditText) promptsView.findViewById(R.id.etRename); + final TextView tvRenameLabel = (TextView) promptsView.findViewById(R.id.tvRenameLabel); + final Wallet wallet = getWallet(); + tvRenameLabel.setText(getString(R.string.prompt_rename, wallet.getAccountLabel())); + + // set dialog message + alertDialogBuilder + .setCancelable(false) + .setPositiveButton(getString(R.string.label_ok), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Helper.hideKeyboardAlways(WalletActivity.this); + String newName = etRename.getText().toString(); + wallet.setAccountLabel(newName); + updateAccountName(); + } + }) + .setNegativeButton(getString(R.string.label_cancel), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Helper.hideKeyboardAlways(WalletActivity.this); + dialog.cancel(); + } + }); + + final AlertDialog dialog = alertDialogBuilder.create(); + Helper.showKeyboard(dialog); + + // accept keyboard "ok" + etRename.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)) { + Helper.hideKeyboardAlways(WalletActivity.this); + String newName = etRename.getText().toString(); + dialog.cancel(); + wallet.setAccountLabel(newName); + updateAccountName(); + return false; + } + return false; + } + }); + + dialog.show(); + } + + @Override + public boolean onNavigationItemSelected(MenuItem item) { + final int id = item.getItemId(); + switch (id) { + case R.id.account_new: + getWallet().addAccount(); + int newIdx = getWallet().numAccounts() - 1; + getWallet().setAccountIndex(newIdx); + Toast.makeText(this, + getString(R.string.accounts_new, newIdx), + Toast.LENGTH_SHORT).show(); + break; + default: + Timber.d("NavigationDrawer ID=%d", id); + int accountIdx = accountIds.indexOf(id); + if (accountIdx >= 0) { + Timber.d("found @%d", accountIdx); + getWallet().setAccountIndex(accountIdx); + } + } + forceUpdate(); + drawer.closeDrawer(GravityCompat.START); + return true; + } } diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java index f9a5be8..2dfb1fd 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java +++ b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java @@ -257,7 +257,7 @@ public class WalletFragment extends Fragment // called from activity public void onRefreshed(final Wallet wallet, final boolean full) { - Timber.d("onRefreshed()"); + Timber.d("onRefreshed(%b)", full); if (full) { List list = wallet.getHistory().getAll(); adapter.setInfos(list); @@ -271,6 +271,7 @@ public class WalletFragment extends Fragment bSend.setVisibility(View.VISIBLE); bSend.setEnabled(true); } + enableAccountsList(true); } boolean walletLoaded = false; @@ -314,7 +315,7 @@ public class WalletFragment extends Fragment if (wallet == null) return; walletTitle = wallet.getName(); String watchOnly = (wallet.isWatchOnly() ? getString(R.string.label_watchonly) : ""); - walletSubtitle = wallet.getAddress().substring(0, 10) + "…" + watchOnly; + walletSubtitle = wallet.getAccountLabel(); activityCallback.setTitle(walletTitle, walletSubtitle); Timber.d("wallet title is %s", walletTitle); } @@ -324,10 +325,13 @@ public class WalletFragment extends Fragment private String walletSubtitle = null; private long unlockedBalance = 0; + private int accountIdx = -1; + private void updateStatus(Wallet wallet) { if (!isAdded()) return; Timber.d("updateStatus()"); - if (walletTitle == null) { + if ((walletTitle == null) || (accountIdx != wallet.getAccountIndex())) { + accountIdx = wallet.getAccountIndex(); setActivityTitle(wallet); } long balance = wallet.getBalance(); @@ -413,9 +417,27 @@ public class WalletFragment extends Fragment super.onResume(); Timber.d("onResume()"); activityCallback.setTitle(walletTitle, walletSubtitle); - activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE); + //activityCallback.setToolbarButton(Toolbar.BUTTON_CLOSE); // TODO: Close button somewhere else + activityCallback.setToolbarButton(Toolbar.BUTTON_NONE); setProgress(syncProgress); setProgress(syncText); showReceive(); } + + @Override + public void onPause() { + enableAccountsList(false); + super.onPause(); + } + + public interface DrawerLocker { + void setDrawerEnabled(boolean enabled); + } + + private void enableAccountsList(boolean enable) { + if (activityCallback instanceof DrawerLocker) { + ((DrawerLocker) activityCallback).setDrawerEnabled(enable); + } + } + } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java index efee88a..e53f3b8 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionHistory.java @@ -17,8 +17,11 @@ package com.m2049r.xmrwallet.model; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import timber.log.Timber; + public class TransactionHistory { static { System.loadLibrary("monerujo"); @@ -26,8 +29,18 @@ public class TransactionHistory { private long handle; - public TransactionHistory(long handle) { + int accountIndex; + + public void setAccountFor(Wallet wallet) { + if (accountIndex != wallet.getAccountIndex()) { + this.accountIndex = wallet.getAccountIndex(); + refreshWithNotes(wallet); + } + } + + public TransactionHistory(long handle, int accountIndex) { this.handle = handle; + this.accountIndex = accountIndex; } public void loadNotes(Wallet wallet) { @@ -36,7 +49,7 @@ public class TransactionHistory { } } - public native int getCount(); + public native int getCount(); // over all accounts/subaddresses //private native long getTransactionByIndexJ(int i); @@ -53,8 +66,23 @@ public class TransactionHistory { loadNotes(wallet); } +// public void refresh() { +// transactions = refreshJ(); +// } + public void refresh() { - transactions = refreshJ(); + List t = refreshJ(); + Timber.d("refreshed %d", t.size()); + for (Iterator iterator = t.iterator(); iterator.hasNext(); ) { + TransactionInfo info = iterator.next(); + if (info.subaddrAccount != accountIndex) { + iterator.remove(); + Timber.d("removed %s", info.hash); + } else { + Timber.d("kept %s", info.hash); + } + } + transactions = t; } private native List refreshJ(); 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 40a0b7d..bf45a2d 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/TransactionInfo.java @@ -53,6 +53,11 @@ public class TransactionInfo implements Parcelable, Comparable } } +// virtual std::set subaddrIndex() const = 0; +// virtual uint32_t subaddrAccount() const = 0; +// virtual std::string label() const = 0; +// virtual uint64_t confirmations() const = 0; + public Direction direction; public boolean isPending; public boolean isFailed; @@ -62,6 +67,7 @@ public class TransactionInfo implements Parcelable, Comparable public String hash; public long timestamp; public String paymentId; + public int subaddrAccount; public long confirmations; public List transfers; @@ -78,6 +84,7 @@ public class TransactionInfo implements Parcelable, Comparable String hash, long timestamp, String paymentId, + int subaddrAccount, long confirmations, List transfers) { this.direction = Direction.values()[direction]; @@ -89,6 +96,7 @@ public class TransactionInfo implements Parcelable, Comparable this.hash = hash; this.timestamp = timestamp; this.paymentId = paymentId; + this.subaddrAccount = subaddrAccount; this.confirmations = confirmations; this.transfers = transfers; } @@ -108,6 +116,7 @@ public class TransactionInfo implements Parcelable, Comparable out.writeString(hash); out.writeLong(timestamp); out.writeString(paymentId); + out.writeInt(subaddrAccount); out.writeLong(confirmations); out.writeList(transfers); out.writeString(txKey); @@ -134,6 +143,7 @@ public class TransactionInfo implements Parcelable, Comparable hash = in.readString(); timestamp = in.readLong(); paymentId = in.readString(); + subaddrAccount = in.readInt(); confirmations = in.readLong(); transfers = in.readArrayList(Transfer.class.getClassLoader()); txKey = in.readString(); 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 69cb27c..a7a1eac 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/Wallet.java @@ -20,12 +20,24 @@ import com.m2049r.xmrwallet.data.TxData; import java.io.File; +import timber.log.Timber; + public class Wallet { static { System.loadLibrary("monerujo"); } - static final String TAG = "Wallet"; + private int accountIndex = 0; + + public int getAccountIndex() { + return accountIndex; + } + + public void setAccountIndex(int accountIndex) { + Timber.d("setAccountIndex(%d)", accountIndex); + this.accountIndex = accountIndex; + getHistory().setAccountFor(this); + } public String getName() { return new File(getPath()).getName(); @@ -38,6 +50,11 @@ public class Wallet { this.handle = handle; } + Wallet(long handle, int accountIndex) { + this.handle = handle; + this.accountIndex = accountIndex; + } + public enum Status { Status_Ok, Status_Error, @@ -66,16 +83,11 @@ public class Wallet { public native boolean setPassword(String password); - private String address = null; - public String getAddress() { - if (address == null) { - address = getAddressJ(); - } - return address; + return getAddressJ(accountIndex); } - private native String getAddressJ(); + private native String getAddressJ(int accountIndex); public native String getPath(); @@ -132,9 +144,21 @@ public class Wallet { //TODO virtual void setTrustedDaemon(bool arg) = 0; //TODO virtual bool trustedDaemon() const = 0; - public native long getBalance(); + public long getBalance() { + return getBalance(accountIndex); + } - public native long getUnlockedBalance(); + public native long getBalance(int accountIndex); + + public native long getBalanceAll(); + + public long getUnlockedBalance() { + return getUnlockedBalance(accountIndex); + } + + public native long getUnlockedBalanceAll(); + + public native long getUnlockedBalance(int accountIndex); public native boolean isWatchOnly(); @@ -207,14 +231,15 @@ public class Wallet { PendingTransaction.Priority priority) { disposePendingTransaction(); int _priority = priority.getValue(); - long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority); + long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority, + accountIndex); pendingTransaction = new PendingTransaction(txHandle); return pendingTransaction; } private native long createTransactionJ(String dst_addr, String payment_id, long amount, int mixin_count, - int priority); + int priority, int accountIndex); public PendingTransaction createSweepUnmixableTransaction() { @@ -241,7 +266,7 @@ public class Wallet { public TransactionHistory getHistory() { if (history == null) { - history = new TransactionHistory(getHistoryJ()); + history = new TransactionHistory(getHistoryJ(), accountIndex); } return history; } @@ -273,4 +298,43 @@ public class Wallet { //virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector &unknown_parameters, std::string &error) = 0; //virtual bool rescanSpent() = 0; + private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941 + + public void addAccount() { + addAccount(NEW_ACCOUNT_NAME); + } + + public native void addAccount(String label); + + public String getAccountLabel() { + return getAccountLabel(accountIndex); + } + + public String getAccountLabel(int accountIndex) { + String label = getSubaddressLabel(accountIndex, 0); + if (label.equals(NEW_ACCOUNT_NAME)) { + String address = getAddressJ(accountIndex); + int len = address.length(); + return address.substring(0, 6) + + "\u2026" + address.substring(len - 6, len); + } else return label; + } + + public native String getSubaddressLabel(int accountIndex, int addressIndex); + + public void setAccountLabel(String label) { + setSubaddressLabel(accountIndex, 0, label); + } + + public void setAccountLabel(int accountIndex, String label) { + setSubaddressLabel(accountIndex, 0, label); + } + + public native void setSubaddressLabel(int accountIndex, int addressIndex, String label); + + public int numAccounts() { + return numSubaddressAccounts(); + } + + public native int numSubaddressAccounts(); } diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java index b0a535e..2ee5a7f 100644 --- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java +++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java @@ -80,6 +80,13 @@ public class WalletManager { private native long createWalletJ(String path, String password, String language, int networkType); + public Wallet openAccount(String path, int accountIndex, String password) { + long walletHandle = openWalletJ(path, password, getNetworkType().getValue()); + Wallet wallet = new Wallet(walletHandle, accountIndex); + manageWallet(wallet); + return wallet; + } + public Wallet openWallet(String path, String password) { long walletHandle = openWalletJ(path, password, getNetworkType().getValue()); Wallet wallet = new Wallet(walletHandle); diff --git a/app/src/main/res/drawable/ic_account_balance_wallet_black_24dp.xml b/app/src/main/res/drawable/ic_account_balance_wallet_black_24dp.xml new file mode 100644 index 0000000..9f5c21c --- /dev/null +++ b/app/src/main/res/drawable/ic_account_balance_wallet_black_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_add_circle_white.xml b/app/src/main/res/drawable/ic_add_circle_white.xml new file mode 100644 index 0000000..c17cc32 --- /dev/null +++ b/app/src/main/res/drawable/ic_add_circle_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_edit_white_24dp.xml b/app/src/main/res/drawable/ic_edit_white_24dp.xml new file mode 100644 index 0000000..46462b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_edit_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_wallet.xml b/app/src/main/res/layout/activity_wallet.xml index 4df48d5..66498cd 100644 --- a/app/src/main/res/layout/activity_wallet.xml +++ b/app/src/main/res/layout/activity_wallet.xml @@ -1,22 +1,41 @@ - + android:fitsSystemWindows="true"> - + android:layout_height="match_parent" + android:background="@drawable/backgound_all" + android:gravity="center_horizontal" + android:orientation="vertical"> - + - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_tx_info.xml b/app/src/main/res/layout/fragment_tx_info.xml index e9c3fbe..b22dd62 100644 --- a/app/src/main/res/layout/fragment_tx_info.xml +++ b/app/src/main/res/layout/fragment_tx_info.xml @@ -171,6 +171,24 @@ android:layout_height="match_parent" android:shrinkColumns="1"> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/drawer_view.xml b/app/src/main/res/menu/drawer_view.xml new file mode 100644 index 0000000..197047d --- /dev/null +++ b/app/src/main/res/menu/drawer_view.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/wallet_menu.xml b/app/src/main/res/menu/wallet_menu.xml index 053a2cb..20c1e35 100644 --- a/app/src/main/res/menu/wallet_menu.xml +++ b/app/src/main/res/menu/wallet_menu.xml @@ -16,6 +16,13 @@ android:title="@string/menu_info" app:showAsAction="never" /> + + View Only Wallet wiederherstellen Wallet mit privaten Schlüsseln wiederherstellen Wallet mit 25 Wörter Seed wiederherstellen + + Open Accounts Drawer + Close Accounts Drawer + Create Account + Accounts + %1$s XMR + Added new account #%1$d + Account # diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a4b28f5..5943fe6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -292,4 +292,12 @@ Orden XMR.TO Pago en BTC activado, toca para más info. CrAzYpass activado, toca para más info. + + Open Accounts Drawer + Close Accounts Drawer + Create Account + Accounts + %1$s XMR + Added new account #%1$d + Account # diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 124e317..f882df5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -310,4 +310,12 @@ Restaurer un portefeuille d\'audit Restaurer un portefeuille depuis la clef privée Restaurer un portefeuille depuis la phrase mnémonique + + Open Accounts Drawer + Close Accounts Drawer + Create Account + Accounts + %1$s XMR + Added new account #%1$d + Account # diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index c13020f..0864410 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -308,4 +308,12 @@ Recupera un portafoglio solo-visualizzazione Recupera un portafoglio dalle chiavi private Recupera un portafoglio da un seed di 25 parole + + Open Accounts Drawer + Close Accounts Drawer + Create Account + Accounts + %1$s XMR + Added new account #%1$d + Account # diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index edc868c..a75bbf4 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -306,4 +306,12 @@ Gjenoprett bare-se lommebok Gjenoprett lommebok fra private nøkler Gjenoprett lommebok fra 25-ord seed + + Open Accounts Drawer + Close Accounts Drawer + Create Account + Accounts + %1$s XMR + Added new account #%1$d + Account # diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 398b295..1dd0d0f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -304,4 +304,12 @@ 恢复只读钱包 从私钥恢复钱包 从25字种子码恢复钱包 + + Open Accounts Drawer + Close Accounts Drawer + Create Account + Accounts + %1$s XMR + Added new account #%1$d + Account # diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 5a75264..b79efe1 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -305,4 +305,12 @@ 回復唯讀錢包 從私鑰回復錢包 從25字種子碼回復錢包 + + Open Accounts Drawer + Close Accounts Drawer + Create Account + Accounts + %1$s XMR + Added new account #%1$d + Account # diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index de83769..0c83ff5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -256,12 +256,16 @@ Create Transaction Error + %1$s XMR + Added new account #%1$d + - Fee %1$s (%1$s) failed - %1$s + %1$s + Account # Timestamp TX ID TX Key @@ -358,4 +362,10 @@ Restore view-only wallet Restore wallet from private keys Restore wallet 25 word seed + + Open Accounts Drawer + Close Accounts Drawer + Create Account + Accounts + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index fa5931f..989031d 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -52,6 +52,10 @@ true + +