cleanup + docs

This commit is contained in:
m2049r 2017-08-20 16:18:59 +02:00
parent 37414be4bf
commit 30e35a895d
18 changed files with 160 additions and 118 deletions

1
.idea/.gitignore vendored
View File

@ -1 +1,2 @@
workspace.xml workspace.xml
markdown-navigator*

View File

@ -3,39 +3,35 @@ Another Android Monero Wallet
### QUICKSTART ### QUICKSTART
- Download APK (Release) and install it - Download APK (Release) and install it
- Copy over synced wallet (all three files) onto sdcard in directory Monerujo (created first time app is started) - Run the App and select "Generate Wallet" to create a new wallet or recover a wallet
- Start app (again) - Advanced users could copy over synced wallet files (all files) onto sdcard in directory Monerujo (created first time App is started)
- see the [FAQ](doc/FAQ.md) - see the [FAQ](doc/FAQ.md)
### Disclaimer ### Disclaimer
This is my first serious Android App. You may loose all your Moneroj if you use this App.
### Random Notes ### Random Notes
- Based off monero v0.10.3.1 with pull requests #2238, #2239 and #2289 applied => so can be used in mainnet! - Based off monero v0.10.3.1 with pull requests #2238, #2239 and #2289 applied => so can be used in mainnet!
- currently only android32 - currently only android32
- currently only use is checking incoming/outgoing transactions - ~~currently only use is checking incoming/outgoing transactions~~
- works in testnet & mainnet (please use your own daemons) - works in testnet & mainnet
- takes forever to sync on mainnet (even with own daemon) - due to 32-bit architecture - takes forever to sync due to 32-bit architecture
- use your own daemon - it's easy - use your own daemon - it's easy
- screen stays on until first sync is complete - screen stays on until first sync is complete
- saves wallet only on first sync - saves wallet only on first sync and when sending transactions or editing notes
- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/ - Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/
### TODO ### TODO
- make it pretty - wallet backup functions
- adjust layout so we can use bigger font sizes (maybe show only 5 decimal places instead of 12 in main view) - adjust layout so we can use bigger font sizes (maybe show only 5 decimal places instead of 12 in main view)
- review visibility of methods/classes - review visibility of methods/classes
- sensible error dialogs (e.g. when no write permissions granted) instead of just crashing on purpose - more sensible error dialogs ~~(e.g. when no write permissions granted) instead of just crashing on purpose~~
- spend monero - not so difficult with wallet api
- check licenses of included libraries; License Dialog - check licenses of included libraries; License Dialog
- ~~provide detailed build instructions for third party binaries~~ - ~~make it pretty~~ (decided to go with "form follows function")
- ~~sensible loading/saving progress bars instead of just freezing up~~ - ~~spend monero - not so difficult with wallet api~~
- ~~figure out how to make it all flow better (loading/saving takes forever and does not run in background)~~
- ~~currently loading in background thread produces segfaults in JNI~~
### Issues ### Issues
- ~~screen rotation crashes the app~~ none :)
- ~~turning the display off/on during sync stops sync~~
### HOW TO BUILD ### HOW TO BUILD
No need to build. Binaries are included: No need to build. Binaries are included:

View File

@ -19,14 +19,13 @@
android:name=".WalletActivity" android:name=".WalletActivity"
android:configChanges="orientation|keyboardHidden" android:configChanges="orientation|keyboardHidden"
android:label="@string/wallet_activity_name" android:label="@string/wallet_activity_name"
android:launchMode="singleTop" android:screenOrientation="portrait" />
android:screenOrientation="portrait"></activity>
<activity <activity
android:name=".LoginActivity" android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden"
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="portrait" android:screenOrientation="portrait">
android:configChanges="orientation|keyboardHidden">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -142,11 +142,11 @@ public class GenerateFragment extends Fragment {
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
if (etWalletMnemonic.length() > 0) { if (etWalletMnemonic.length() > 0) {
etWalletRestoreHeight.setVisibility(View.VISIBLE); etWalletRestoreHeight.setVisibility(View.VISIBLE);
etWalletAddress.setVisibility(View.INVISIBLE); etWalletAddress.setVisibility(View.GONE);
} else { } else {
etWalletAddress.setVisibility(View.VISIBLE); etWalletAddress.setVisibility(View.VISIBLE);
if (etWalletAddress.length() == 0) { if (etWalletAddress.length() == 0) {
etWalletRestoreHeight.setVisibility(View.INVISIBLE); etWalletRestoreHeight.setVisibility(View.GONE);
} else { } else {
etWalletRestoreHeight.setVisibility(View.VISIBLE); etWalletRestoreHeight.setVisibility(View.VISIBLE);
} }
@ -193,10 +193,10 @@ public class GenerateFragment extends Fragment {
etWalletMnemonic.setVisibility(View.INVISIBLE); etWalletMnemonic.setVisibility(View.INVISIBLE);
etWalletRestoreHeight.setVisibility(View.VISIBLE); etWalletRestoreHeight.setVisibility(View.VISIBLE);
} else { } else {
llRestoreKeys.setVisibility(View.INVISIBLE); llRestoreKeys.setVisibility(View.GONE);
etWalletMnemonic.setVisibility(View.VISIBLE); etWalletMnemonic.setVisibility(View.VISIBLE);
if (etWalletMnemonic.length() == 0) { if (etWalletMnemonic.length() == 0) {
etWalletRestoreHeight.setVisibility(View.INVISIBLE); etWalletRestoreHeight.setVisibility(View.GONE);
} else { } else {
etWalletRestoreHeight.setVisibility(View.VISIBLE); etWalletRestoreHeight.setVisibility(View.VISIBLE);
} }
@ -280,8 +280,8 @@ public class GenerateFragment extends Fragment {
private boolean addressOk() { private boolean addressOk() {
String address = etWalletAddress.getText().toString(); String address = etWalletAddress.getText().toString();
// TODO only accept address from the correct net boolean testnet = WalletManager.getInstance().isTestNet();
return ((address.length() == 95) && ("49A".indexOf(address.charAt(0)) >= 0)); return ((address.length() == 95) && ((testnet ? "9A" : "4").indexOf(address.charAt(0)) >= 0));
} }
private boolean viewKeyOk() { private boolean viewKeyOk() {
@ -318,19 +318,19 @@ public class GenerateFragment extends Fragment {
// figure out how we want to create this wallet // figure out how we want to create this wallet
// A. from scratch // A. from scratch
if ((seed.length() == 0) && (address.length() == 0)) { if ((seed.length() == 0) && (address.length() == 0)) {
bGenerate.setVisibility(View.INVISIBLE); bGenerate.setVisibility(View.GONE);
activityCallback.onGenerate(name, password); activityCallback.onGenerate(name, password);
} else } else
// B. from seed // B. from seed
if (mnemonicOk()) { if (mnemonicOk()) {
bGenerate.setVisibility(View.INVISIBLE); bGenerate.setVisibility(View.GONE);
activityCallback.onGenerate(name, password, seed, height); activityCallback.onGenerate(name, password, seed, height);
} else } else
// C. from keys // C. from keys
if (addressOk() && viewKeyOk() && (spendKeyOk())) { if (addressOk() && viewKeyOk() && (spendKeyOk())) {
String viewKey = etWalletViewKey.getText().toString(); String viewKey = etWalletViewKey.getText().toString();
String spendKey = etWalletSpendKey.getText().toString(); String spendKey = etWalletSpendKey.getText().toString();
bGenerate.setVisibility(View.INVISIBLE); bGenerate.setVisibility(View.GONE);
activityCallback.onGenerate(name, password, address, viewKey, spendKey, height); activityCallback.onGenerate(name, password, address, viewKey, spendKey, height);
} else } else
// D. none of the above :) // D. none of the above :)

View File

@ -36,6 +36,7 @@ public class GenerateReviewFragment extends Fragment {
static final String TAG = "GenerateReviewFragment"; static final String TAG = "GenerateReviewFragment";
static final public String VIEW_DETAILS = "details"; static final public String VIEW_DETAILS = "details";
static final public String VIEW_ACCEPT = "accept"; static final public String VIEW_ACCEPT = "accept";
static final public String VIEW_WALLET = "wallet";
ProgressBar pbProgress; ProgressBar pbProgress;
TextView tvWalletName; TextView tvWalletName;
@ -75,12 +76,15 @@ public class GenerateReviewFragment extends Fragment {
showProgress(); showProgress();
Bundle b = getArguments(); Bundle b = getArguments();
String name = b.getString("name");
String password = b.getString("password");
String type = b.getString("type"); String type = b.getString("type");
tvWalletName.setText(new File(name).getName()); if (!type.equals(VIEW_WALLET)) {
show(name, password, type); String name = b.getString("name");
String password = b.getString("password");
tvWalletName.setText(new File(name).getName());
show(name, password, type);
} else {
show(walletCallback.getWallet(), null, type);
}
return view; return view;
} }
@ -88,7 +92,7 @@ public class GenerateReviewFragment extends Fragment {
String name = tvWalletName.getText().toString(); String name = tvWalletName.getText().toString();
String password = tvWalletPassword.getText().toString(); String password = tvWalletPassword.getText().toString();
bAccept.setEnabled(false); bAccept.setEnabled(false);
activityCallback.onAccept(name, password); acceptCallback.onAccept(name, password);
} }
private void show(final String walletPath, final String password, final String type) { private void show(final String walletPath, final String password, final String type) {
@ -96,50 +100,55 @@ public class GenerateReviewFragment extends Fragment {
new Runnable() { new Runnable() {
@Override @Override
public void run() { public void run() {
Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password); final Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password);
final String name = wallet.getName();
final String seed = wallet.getSeed();
final String address = wallet.getAddress();
final String view = wallet.getSecretViewKey();
final String spend = wallet.isWatchOnly() ? "" : "not available - use seed for recovery";
wallet.close();
getActivity().runOnUiThread(new Runnable() { getActivity().runOnUiThread(new Runnable() {
public void run() { public void run() {
if (type.equals(GenerateReviewFragment.VIEW_ACCEPT)) { show(wallet, password, type);
tvWalletPassword.setText(password);
bAccept.setVisibility(View.VISIBLE);
bAccept.setEnabled(true);
}
tvWalletName.setText(name);
tvWalletAddress.setText(address);
tvWalletMnemonic.setText(seed);
tvWalletViewKey.setText(view);
if (spend.length() > 0) { //TODO should be == 64, but spendkey is not in the API yet
tvWalletSpendKey.setText(spend);
} else {
tvWalletSpendKey.setText(getString(R.string.generate_wallet_watchonly));
}
hideProgress();
} }
}); });
wallet.close();
} }
} }
, "DetailsReview", MoneroHandlerThread.THREAD_STACK_SIZE).start(); , "DetailsReview", MoneroHandlerThread.THREAD_STACK_SIZE).start();
} }
GenerateReviewFragment.Listener activityCallback; private void show(final Wallet wallet, final String password, final String type) {
if (type.equals(GenerateReviewFragment.VIEW_ACCEPT)) {
tvWalletPassword.setText(password);
bAccept.setVisibility(View.VISIBLE);
bAccept.setEnabled(true);
}
tvWalletName.setText(wallet.getName());
tvWalletAddress.setText(wallet.getAddress());
tvWalletMnemonic.setText(wallet.getSeed());
tvWalletViewKey.setText(wallet.getSecretViewKey());
String spend = wallet.isWatchOnly() ? "" : "not available - use seed for recovery";
if (spend.length() > 0) { //TODO should be == 64, but spendkey is not in the API yet
tvWalletSpendKey.setText(spend);
} else {
tvWalletSpendKey.setText(getString(R.string.generate_wallet_watchonly));
}
hideProgress();
}
GenerateReviewFragment.Listener acceptCallback = null;
GenerateReviewFragment.ListenerWithWallet walletCallback = null;
public interface Listener { public interface Listener {
void onAccept(String name, String password); void onAccept(String name, String password);
} }
public interface ListenerWithWallet {
Wallet getWallet();
}
@Override @Override
public void onAttach(Context context) { public void onAttach(Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof GenerateReviewFragment.Listener) { if (context instanceof GenerateReviewFragment.Listener) {
this.activityCallback = (GenerateReviewFragment.Listener) context; this.acceptCallback = (GenerateReviewFragment.Listener) context;
} else if (context instanceof GenerateReviewFragment.ListenerWithWallet) {
this.walletCallback = (GenerateReviewFragment.ListenerWithWallet) context;
} else { } else {
throw new ClassCastException(context.toString() throw new ClassCastException(context.toString()
+ " must implement Listener"); + " must implement Listener");

View File

@ -16,7 +16,6 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentManager; import android.app.FragmentManager;
@ -29,6 +28,7 @@ import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -40,7 +40,6 @@ import android.widget.Toast;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import java.io.File; import java.io.File;
@ -63,6 +62,11 @@ public class LoginActivity extends AppCompatActivity
if (savedInstanceState != null) { if (savedInstanceState != null) {
return; return;
} }
Toolbar toolbar = (Toolbar) findViewById(R.id.tbLogin);
toolbar.setTitle(R.string.login_activity_name);
setSupportActionBar(toolbar);
if (Helper.getWritePermission(this)) { if (Helper.getWritePermission(this)) {
startLoginFragment(); startLoginFragment();
} else { } else {
@ -235,7 +239,6 @@ public class LoginActivity extends AppCompatActivity
startReviewFragment(b); startReviewFragment(b);
} }
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
Log.d(TAG, "onRequestPermissionsResult()"); Log.d(TAG, "onRequestPermissionsResult()");
@ -420,9 +423,10 @@ public class LoginActivity extends AppCompatActivity
boolean copyWallet(File dstDir, File srcDir, String name) { boolean copyWallet(File dstDir, File srcDir, String name) {
boolean success = false; boolean success = false;
try { try {
// TODO: the cache is corrupt if we recover (!!) // the cache is corrupt if we recover (!!)
// TODO: the cache is ok if we immediately to a full refresh() // the cache is ok if we immediately do a full refresh()
// TODO recoveryheight is ignored but not on watchonly wallet ?! - find out why // recoveryheight is ignored but not on watchonly wallet ?! - find out why
// so we just ignore the cache file and rebuild it on first sync
//copyFile(dstDir, srcDir, name); //copyFile(dstDir, srcDir, name);
copyFile(dstDir, srcDir, name + ".keys"); copyFile(dstDir, srcDir, name + ".keys");
copyFile(dstDir, srcDir, name + ".address.txt"); copyFile(dstDir, srcDir, name + ".address.txt");

View File

@ -28,17 +28,21 @@ import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.os.PowerManager; import android.os.PowerManager;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.WalletService; import com.m2049r.xmrwallet.service.WalletService;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.TxData; import com.m2049r.xmrwallet.util.TxData;
public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener, public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener,
WalletService.Observer, SendFragment.Listener, TxFragment.Listener { WalletService.Observer, SendFragment.Listener, TxFragment.Listener, GenerateReviewFragment.ListenerWithWallet {
private static final String TAG = "WalletActivity"; private static final String TAG = "WalletActivity";
static final int MIN_DAEMON_VERSION = 65544; static final int MIN_DAEMON_VERSION = 65544;
@ -46,6 +50,8 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
public static final String REQUEST_ID = "id"; public static final String REQUEST_ID = "id";
public static final String REQUEST_PW = "pw"; public static final String REQUEST_PW = "pw";
Toolbar tbWallet;
private boolean synced = false; private boolean synced = false;
@Override @Override
@ -109,21 +115,29 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
Log.d(TAG, "onCreate()"); Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.wallet_activity); setContentView(R.layout.wallet_activity);
if (savedInstanceState != null) {
return;
}
tbWallet = (Toolbar) findViewById(R.id.tbWallet);
tbWallet.setTitle(R.string.app_name);
setSupportActionBar(tbWallet);
tbWallet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onWalletDetails();
}
});
Fragment walletFragment = new WalletFragment(); Fragment walletFragment = new WalletFragment();
getFragmentManager().beginTransaction() getFragmentManager().beginTransaction()
.add(R.id.fragment_container, walletFragment).commit(); .add(R.id.fragment_container, walletFragment).commit();
Log.d(TAG, "fragment added"); Log.d(TAG, "fragment added");
if (savedInstanceState != null) {
return;
}
startWalletService(); startWalletService();
Log.d(TAG, "onCreate() done."); Log.d(TAG, "onCreate() done.");
} }
public Wallet getWallet() { public Wallet getWallet() {
if (mBoundService == null) throw new IllegalStateException("WalletService not bound."); if (mBoundService == null) throw new IllegalStateException("WalletService not bound.");
return mBoundService.getWallet(); return mBoundService.getWallet();
@ -256,7 +270,7 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
@Override @Override
public void setTitle(String title) { public void setTitle(String title) {
super.setTitle(title); tbWallet.setTitle(title);
} }
@Override @Override
@ -522,4 +536,13 @@ public class WalletActivity extends AppCompatActivity implements WalletFragment.
transaction.addToBackStack(stackName); transaction.addToBackStack(stackName);
transaction.commit(); transaction.commit();
} }
private void onWalletDetails() {
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof WalletFragment) {
Bundle extras = new Bundle();
extras.putString("type", GenerateReviewFragment.VIEW_WALLET);
replaceFragment(new GenerateReviewFragment(), null, extras);
}
}
} }

View File

@ -116,7 +116,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
updateStatus(wallet); updateStatus(wallet);
} }
public void onSynced() { // TODO watchonly public void onSynced() {
if (!activityCallback.isWatchOnly()) { if (!activityCallback.isWatchOnly()) {
bSend.setVisibility(View.VISIBLE); bSend.setVisibility(View.VISIBLE);
bSend.setEnabled(true); bSend.setEnabled(true);

View File

@ -81,6 +81,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
public void setInfos(List<TransactionInfo> data) { public void setInfos(List<TransactionInfo> data) {
// TODO do stuff with data so we can really recycle elements (i.e. add only new tx) // TODO do stuff with data so we can really recycle elements (i.e. add only new tx)
// as the TransactionInfo items are always recreated, we cannot recycle
this.infoItems.clear(); this.infoItems.clear();
if (data != null) { if (data != null) {
Log.d(TAG, "setInfos " + data.size()); Log.d(TAG, "setInfos " + data.size());
@ -135,9 +136,8 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
void bind(int position) { void bind(int position) {
this.infoItem = infoItems.get(position); this.infoItem = infoItems.get(position);
String displayAmount = Wallet.getDisplayAmount(infoItem.amount); String displayAmount = Wallet.getDisplayAmount(infoItem.amount);
// TODO fix this with i8n code // TODO fix this with i8n code but cryptonote::print_money always uses '.' for decimal point
String amountParts[] = displayAmount.split("\\."); String amountParts[] = displayAmount.split("\\.");
// TODO what if there is no decimal point?
this.tvAmount.setText(amountParts[0]); this.tvAmount.setText(amountParts[0]);
this.tvAmountDecimal.setText(amountParts[1]); this.tvAmountDecimal.setText(amountParts[1]);

View File

@ -38,7 +38,7 @@ public class WalletManager {
// no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it) // no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
private static WalletManager Instance = null; private static WalletManager Instance = null;
public static WalletManager getInstance() { // TODO not threadsafe public static synchronized WalletManager getInstance() {
if (WalletManager.Instance == null) { if (WalletManager.Instance == null) {
WalletManager.Instance = new WalletManager(); WalletManager.Instance = new WalletManager();
} }

View File

@ -120,12 +120,11 @@ public class WalletService extends Service {
updateDaemonState(wallet, wallet.isSynchronized() ? height : 0); updateDaemonState(wallet, wallet.isSynchronized() ? height : 0);
if (!wallet.isSynchronized()) { if (!wallet.isSynchronized()) {
// we want to see our transactions as they come in // we want to see our transactions as they come in
Log.d(TAG, "newBlock() refresh history");
wallet.getHistory().refresh(); wallet.getHistory().refresh();
Log.d(TAG, "newBlock() history refreshed");
int txCount = wallet.getHistory().getCount(); int txCount = wallet.getHistory().getCount();
if (txCount > lastTxCount) { if (txCount > lastTxCount) {
lastTxCount = txCount; // TODO maybe do this later // update the transaction list only if we have more than before
lastTxCount = txCount;
fullRefresh = true; fullRefresh = true;
} }
} }
@ -173,10 +172,9 @@ public class WalletService extends Service {
// these calls really connect to the daemon - wasting time // these calls really connect to the daemon - wasting time
daemonHeight = wallet.getDaemonBlockChainHeight(); daemonHeight = wallet.getDaemonBlockChainHeight();
if (daemonHeight > 0) { if (daemonHeight > 0) {
// if we get a valid height, the obviously we are connected // if we get a valid height, then obviously we are connected
connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Connected; connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Connected;
} else { } else {
// TODO: or connectionStatus = wallet.getConnectionStatus(); ?
connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Disconnected; connectionStatus = Wallet.ConnectionStatus.ConnectionStatus_Disconnected;
} }
} }
@ -485,7 +483,6 @@ public class WalletService extends Service {
listener = null; listener = null;
} }
stopSelf(); stopSelf();
// TODO ensure the Looper & thread actually stop and go away?
} }
private Wallet loadWallet(String walletName, String walletPassword) { private Wallet loadWallet(String walletName, String walletPassword) {

View File

@ -60,7 +60,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:visibility="invisible"> android:visibility="gone">
<EditText <EditText
android:id="@+id/etWalletViewKey" android:id="@+id/etWalletViewKey"
@ -92,7 +92,7 @@
android:inputType="number" android:inputType="number"
android:textAlignment="center" android:textAlignment="center"
android:textSize="16sp" android:textSize="16sp"
android:visibility="invisible" /> android:visibility="gone" />
<Button <Button
android:id="@+id/bGenerate" android:id="@+id/bGenerate"

View File

@ -3,11 +3,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical">
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin" <android.support.v7.widget.Toolbar
android:paddingRight="@dimen/activity_horizontal_margin" android:id="@+id/tbLogin"
android:paddingTop="@dimen/activity_vertical_margin"> android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container" android:id="@+id/fragment_container"

View File

@ -3,11 +3,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical">
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin" <android.support.v7.widget.Toolbar
android:paddingRight="@dimen/activity_horizontal_margin" android:id="@+id/tbWallet"
android:paddingTop="@dimen/activity_vertical_margin"> android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:elevation="4dp" />
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container" android:id="@+id/fragment_container"

View File

@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -39,6 +38,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="4dp" android:layout_marginEnd="4dp"
android:text="@string/label_unlockedBalance" android:text="@string/label_unlockedBalance"
android:textColor="@color/colorAccent"
android:textSize="10sp" android:textSize="10sp"
app:layout_constraintBaseline_toBaselineOf="@+id/tvUnlockedBalance" app:layout_constraintBaseline_toBaselineOf="@+id/tvUnlockedBalance"
app:layout_constraintRight_toLeftOf="@+id/tvUnlockedBalance" /> app:layout_constraintRight_toLeftOf="@+id/tvUnlockedBalance" />
@ -49,6 +49,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="00000000.000000000000" android:text="00000000.000000000000"
android:textColor="@color/colorAccent"
app:layout_constraintRight_toRightOf="@+id/tvBalance" app:layout_constraintRight_toRightOf="@+id/tvBalance"
app:layout_constraintTop_toBottomOf="@+id/tvBalance" /> app:layout_constraintTop_toBottomOf="@+id/tvBalance" />
@ -61,7 +62,7 @@
android:text="Loading..." android:text="Loading..."
android:textSize="10sp" android:textSize="10sp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintBaseline_toBaselineOf="@+id/tvBalance" />
<TextView <TextView
android:id="@+id/tvBlockHeightProgress" android:id="@+id/tvBlockHeightProgress"
@ -72,7 +73,7 @@
android:text="Loading..." android:text="Loading..."
android:textSize="10sp" android:textSize="10sp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvConnectionStatus" /> app:layout_constraintBaseline_toBaselineOf="@+id/tvUnlockedBalance"/>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>
@ -101,13 +102,13 @@
android:progress="0" /> android:progress="0" />
</LinearLayout> </LinearLayout>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" <android.support.v7.widget.RecyclerView
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/list" android:id="@+id/list"

View File

@ -57,7 +57,7 @@
<string name="generate_title">Generate Wallet</string> <string name="generate_title">Generate Wallet</string>
<string name="generate_name_hint">Wallet Name</string> <string name="generate_name_hint">Wallet Name</string>
<string name="generate_password_hint">Wallet Password</string> <string name="generate_password_hint">Wallet Password</string>
<string name="generate_buttonGenerate">Do it already!</string> <string name="generate_buttonGenerate">Make me a wallet already!</string>
<string name="generate_seed">Mnemonic Seed</string> <string name="generate_seed">Mnemonic Seed</string>
<string name="generate_button_accept">I have noted the above\nNow, I want to loose all my money!</string> <string name="generate_button_accept">I have noted the above\nNow, I want to loose all my money!</string>

View File

@ -1,8 +1,8 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. --> <item name="windowActionBar">false</item>
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>

View File

@ -1,16 +1,28 @@
# FAQ # FAQ
## Do you have any screenshots of what it looks like and how it works? ## What features does it have?
### [Select Wallet](images/A-wallet_selection.png) - Testnet and Mainnet
- Generate new wallets
- Recover wallets form nmemonic seed or from keys
- Create Watch Only wallets from address + viewkey
- Multiple wallets
- View wallet details (address, keys, etc.)
- View transactions inlcuding details and copy to clipboard
- Spend Moneroj (only on testnet - someone will loose money and want to blame me. No thanks!)
- Manually import existing wallet (by copying them to the Monerujo folder)
## Do you have any screenshots of what it looks like and how it works?
Here are some old screenshots
#### [Select Wallet](images/A-wallet_selection.png)
Here you see a list of installed wallets and an entry field at the top to enter the daemon address. To the right there is a pushbutton to change between testnet and mainnet. The entered daemon is saved and displayed according to the state of this button. Here you see a list of installed wallets and an entry field at the top to enter the daemon address. To the right there is a pushbutton to change between testnet and mainnet. The entered daemon is saved and displayed according to the state of this button.
### [Wallet Password](images/B-enter_password.png) #### [Wallet Password](images/B-enter_password.png)
After selecting the wallet, the password is entered. After selecting the wallet, the password is entered.
### [Wallet Syncing](images/C-wallet_syncing.png) #### [Wallet Syncing](images/C-wallet_syncing.png)
After some seconds the wallet is displyed with it's last known state and synced to the network. If it says "disconnected" or takes forever to show this screen then the entered daemon is wrong or unreachable. (Yes, I need to check the daemon availability on the login screen ...) Go back, and check that. After some seconds the wallet is displyed with it's last known state and synced to the network. If it says "disconnected" or takes forever to show this screen then the entered daemon is wrong or unreachable. (Yes, I need to check the daemon availability on the login screen ...) Go back, and check that.
@ -22,15 +34,9 @@ The balance is updated during sync.
When the blockchain is synced, the screen shows "Synced" and the current blockchain height. When new blocks become available they are also synced and new transactions are displayed. When the blockchain is synced, the screen shows "Synced" and the current blockchain height. When new blocks become available they are also synced and new transactions are displayed.
## What features does it have?
That's about it. Select a wallet and show the balance. Behind the scenes it keeps in sync with the blockchain while the app is open. So currently it is a view only wallet. You can use it to monitor your wallets on the go.
In future it will have the possibility of executing transactions. And generating wallets. Technically, it can generate wallets now, but they are pointless since you need another client to make transactions anyway - so you can make the wallets on the other client.
## What files do I need to copy? ## What files do I need to copy?
You need to copy the wallet files from you current Monero client. These are: If you want to use existing wallet files, you need to copy the wallet files from you current Monero client. These are:
``` ```
WalletName WalletName
WalletName.address.txt WalletName.address.txt
@ -43,4 +49,4 @@ This depends on your installation - you could search for them in your home direc
### What if don't have these files? ### What if don't have these files?
As this is a view-only App right now, you need another client for generating wallets and sending transactions. This will change soon<sup>TM</sup>. Keep calm and make a new wallet.