1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-02 15:53:04 +02:00

Compare commits

...

30 Commits

Author SHA1 Message Date
m2049r
03c5569f91 Sexy UI (#104)
Version 29/1.1.2-alpha
2017-10-31 22:35:23 +01:00
m2049r
7b1e6a89ba Android Studio 3.0 2017-10-26 09:18:13 +02:00
m2049r
ff8a3ee7c8 JNI_FALSE -> NULL in GetStringUTFChars()
increased version to 26 / 1.0.3
2017-10-22 12:08:38 +02:00
m2049r
dcbaa35b57 avoid calling isAddressValid with null address 2017-10-17 14:48:45 +02:00
Jonathan Cross
3466116e2a Adding note about name of backups folder changing. (#99) 2017-10-07 20:07:58 +02:00
Steve Divskinsy
59e87e2bfe Fixed spelling mistake (#98)
Minor spelling mistake when archiving
2017-10-07 20:05:12 +02:00
m2049r
e8b749af3b Bugfix Service restart after OS kill (#96)
* single open wallet

* prevent WalletService & Activity from restarting
removed stray png
2017-10-03 22:45:03 +02:00
m2049r
f282f01edd wallet updateStatus only when added (#95) 2017-10-03 19:04:45 +02:00
KeeJef
f16afdbb19 Some spelling and grammar changes (#94) 2017-10-03 18:39:29 +02:00
m2049r
ccacec9d0b Added explicit info about keeping mnemonic seed safe (#92) 2017-09-30 09:17:47 +02:00
m2049r
9ebacb8528 corrected backups folder 2017-09-29 23:20:38 +02:00
m2049r
49a9f84226 new version 23 2017-09-29 19:50:59 +02:00
m2049r
91b4aaad60 removed unnecessary permission 2017-09-29 19:44:43 +02:00
m2049r
c4ce12ceff Explain permissions 2017-09-29 19:41:22 +02:00
m2049r
96cf9ee95e new version (#91) 2017-09-29 19:21:11 +02:00
m2049r
00852da9f7 Add some Help (#90)
* simple help for create wallet

* toast that selecting seed or spendkey is not allowed
2017-09-29 19:18:07 +02:00
m2049r
15b90e7ebc instructions now cater for zeromq & non-zermq monero 2017-09-28 09:30:15 +02:00
m2049r
e270b277a5 Fixup wallet locations (#89)
* create wallets directly in wallet dir, renamed backup dir

* new version + notes
2017-09-27 23:01:37 +02:00
m2049r
2842a4042f Update BUILDING-external-libs.md 2017-09-27 19:59:02 +02:00
m2049r
1614c0ab84 Update to build master (post zeromq)
zeromq, boost back to 1.58, other tweaks
2017-09-27 19:57:27 +02:00
m2049r
dceca0e198 new version 2017-09-26 22:58:08 +02:00
m2049r
94947de9ef Minor fixes (#88)
* another attempt to fix android.view.WindowManager

* trim entered daemon
2017-09-26 21:41:58 +02:00
m2049r
10ea6d8e0c Update FAQ.md 2017-09-26 08:02:55 +02:00
m2049r
0d03b75785 Poppins license (#86) 2017-09-25 23:12:43 +02:00
m2049r
a3db07c6a7 Bugfixes (#85)
* android.view.WindowManager

* refactor

* lock_guard listener

* new version
2017-09-25 22:53:16 +02:00
m2049r
6678222202 Bugfix: Generate screen is now scrollable (#84)
* generate+detail screens made scrollable

* new version
2017-09-23 22:26:12 +02:00
m2049r
793d984200 Bugfix: crashing on android 5 & 6 (new wallet, receive, ...) (#83)
* getAddress() mess fixed

* v0.8.0.3
2017-09-23 18:23:41 +02:00
m2049r
cc46dc06c4 Bugfixes (#82)
* cache getAddress()

* savedInstanceState handling

* v0.8.0.2
2017-09-23 14:16:30 +02:00
m2049r
be236cce1b new bugfix version 2017-09-23 11:09:27 +02:00
m2049r
af18a89a5e Bugfixes based on ANR reports from google (#81)
* remove id in toolbar include (NPE on some devices)

* deal with progress when activity closed
2017-09-23 11:06:10 +02:00
174 changed files with 5841 additions and 2352 deletions

1
.idea/.gitignore generated vendored
View File

@@ -1,2 +1,3 @@
workspace.xml
markdown-*
misc.xml

View File

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

View File

@@ -1,9 +0,0 @@
<component name="libraryTable">
<library name="constraint-layout-solver-1.0.2">
<CLASSES>
<root url="jar://$USER_HOME$/AppData/Local/Android/Sdk/extras/m2repository/com/android/support/constraint/constraint-layout-solver/1.0.2/constraint-layout-solver-1.0.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

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

View File

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

View File

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

46
.idea/misc.xml generated
View File

@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@@ -14,7 +14,7 @@ You may lose all your Moneroj if you use this App. Be cautious when spending on
- Based off monero v0.11.0.0 with PR #2289 applied
- currently only android32 (runs on 64-bit as well)
- works on the testnet & mainnet
- takes forever to sync due to 32-bit architecture
- sync is slow due to 32-bit architecture
- use your own daemon - it's easy
- screen stays on until first sync is complete
- saves wallet only on first sync and when sending transactions or editing notes
@@ -25,11 +25,13 @@ You may lose all your Moneroj if you use this App. Be cautious when spending on
- more sensible error dialogs
### Issues / Pitfalls
- The backups folder is now called "backups" and not ".backups" - which in most file explorers was a hidden folder
- Wallets are now created directly in the "monerujo" folder, and not in the ".new" folder as before
- You may want to check the old folders with a file browsing app and delete the ".new" and ".backups" folders AFTER moving neccessary wallet files to the new locations. Or simply make new backups from within Monerujo.
- Also note, that on some devices the backups will only be visible on a PC over USB after a reboot of the device (it's an Android bug/feature)
- Created wallets on a private testnet are unusable because the restore height is set to that
of the "real" testnet. After creating a new wallet, make a **new** one by recovering from the seed.
The official monero client shows the same behaviour.
- In rare occasions the monero core code returns a wallet address with corrupted characters -
in these cases Monerujo crashes on purpose to make sure nothing bad happens
### HOW TO BUILD
No need to build. Binaries are included:

View File

@@ -3,13 +3,13 @@ apply plugin: 'witness'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
versionCode 13
versionName "0.8"
versionCode 29
versionName "1.1.2-alpha"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {

View File

@@ -3,7 +3,6 @@
package="com.m2049r.xmrwallet">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -19,17 +19,26 @@ package com.m2049r.xmrwallet;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
public class GenerateReviewFragment extends Fragment {
@@ -38,6 +47,8 @@ public class GenerateReviewFragment extends Fragment {
static final public String VIEW_TYPE_ACCEPT = "accept";
static final public String VIEW_TYPE_WALLET = "wallet";
ScrollView scrollview;
ProgressBar pbProgress;
TextView tvWalletName;
TextView tvWalletPassword;
@@ -45,14 +56,18 @@ public class GenerateReviewFragment extends Fragment {
TextView tvWalletMnemonic;
TextView tvWalletViewKey;
TextView tvWalletSpendKey;
ImageButton bCopyAddress;
LinearLayout llAdvancedInfo;
Button bAdvancedInfo;
Button bAccept;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.gen_review_fragment, container, false);
View view = inflater.inflate(R.layout.fragment_review, container, false);
scrollview = (ScrollView) view.findViewById(R.id.scrollview);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
tvWalletName = (TextView) view.findViewById(R.id.tvWalletName);
tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword);
@@ -60,6 +75,9 @@ public class GenerateReviewFragment extends Fragment {
tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey);
tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey);
tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
bAccept = (Button) view.findViewById(R.id.bAccept);
@@ -73,6 +91,36 @@ public class GenerateReviewFragment extends Fragment {
acceptWallet();
}
});
view.findViewById(R.id.bCopyViewKey).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyViewKey();
}
});
bCopyAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
copyAddress();
}
});
view.findViewById(R.id.bCopySeed).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
nocopy();
}
});
view.findViewById(R.id.bCopySepndKey).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
nocopy();
}
});
view.findViewById(R.id.bAdvancedInfo).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showAdvancedInfo();
}
});
showProgress();
@@ -85,6 +133,31 @@ public class GenerateReviewFragment extends Fragment {
return view;
}
void copyViewKey() {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_viewkey), tvWalletViewKey.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_viewkey), Toast.LENGTH_SHORT).show();
}
void copyAddress() {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_address), tvWalletAddress.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
}
void nocopy() {
Toast.makeText(getActivity(), getString(R.string.message_nocopy), Toast.LENGTH_SHORT).show();
}
void showAdvancedInfo() {
llAdvancedInfo.setVisibility(View.VISIBLE);
bAdvancedInfo.setVisibility(View.GONE);
scrollview.post(new Runnable() {
@Override
public void run() {
scrollview.fullScroll(ScrollView.FOCUS_DOWN);
}
});
}
String type;
private void acceptWallet() {
@@ -130,7 +203,7 @@ public class GenerateReviewFragment extends Fragment {
address = wallet.getAddress();
seed = wallet.getSeed();
viewKey = wallet.getSecretViewKey();
spendKey = isWatchOnly ? getActivity().getString(R.string.watchonly_label) : wallet.getSecretSpendKey();
spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey();
isWatchOnly = wallet.isWatchOnly();
if (closeWallet) wallet.close();
return true;
@@ -139,6 +212,7 @@ public class GenerateReviewFragment extends Fragment {
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (!isAdded()) return; // never mind
tvWalletName.setText(name);
if (result) {
if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) {
@@ -150,9 +224,14 @@ public class GenerateReviewFragment extends Fragment {
tvWalletMnemonic.setText(seed);
tvWalletViewKey.setText(viewKey);
tvWalletSpendKey.setText(spendKey);
bAdvancedInfo.setVisibility(View.VISIBLE);
bCopyAddress.setEnabled(true);
bCopyAddress.setImageResource(R.drawable.ic_content_copy_black_24dp);
activityCallback.setTitle(name, getString(R.string.details_title));
activityCallback.setToolbarButton(
GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK);
} else {
// TODO show proper error message
// TODO end the fragment
// TODO show proper error message and/or end the fragment?
tvWalletAddress.setText(status.toString());
tvWalletMnemonic.setText(status.toString());
tvWalletViewKey.setText(status.toString());
@@ -162,41 +241,69 @@ public class GenerateReviewFragment extends Fragment {
}
}
GenerateReviewFragment.Listener acceptCallback = null;
GenerateReviewFragment.ListenerWithWallet walletCallback = null;
Listener activityCallback = null;
AcceptListener acceptCallback = null;
ListenerWithWallet walletCallback = null;
public interface Listener {
void setTitle(String title, String subtitle);
void setToolbarButton(int type);
}
public interface AcceptListener {
void onAccept(String name, String password);
}
public interface ListenerWithWallet {
Wallet getWallet();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof GenerateReviewFragment.Listener) {
this.acceptCallback = (GenerateReviewFragment.Listener) context;
} else if (context instanceof GenerateReviewFragment.ListenerWithWallet) {
this.walletCallback = (GenerateReviewFragment.ListenerWithWallet) context;
} else {
throw new ClassCastException(context.toString()
+ " must implement Listener");
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
}
if (context instanceof AcceptListener) {
this.acceptCallback = (AcceptListener) context;
}
if (context instanceof ListenerWithWallet) {
this.walletCallback = (ListenerWithWallet) context;
}
}
@Override
public void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
String name = tvWalletName.getText().toString();
if (name.isEmpty()) name = null;
activityCallback.setTitle(name, getString(R.string.details_title));
activityCallback.setToolbarButton(
GenerateReviewFragment.VIEW_TYPE_ACCEPT.equals(type) ? Toolbar.BUTTON_NONE : Toolbar.BUTTON_BACK);
}
public void showProgress() {
pbProgress.setIndeterminate(true);
pbProgress.setVisibility(View.VISIBLE);
}
public void hideProgress() {
pbProgress.setVisibility(View.INVISIBLE);
pbProgress.setVisibility(View.GONE);
}
boolean backOk() {
return !type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT);
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.wallet_details_menu, menu);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,6 @@ import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.Result;
import com.m2049r.xmrwallet.util.BarcodeData;
import me.dm7.barcodescanner.zxing.ZXingScannerView;
@@ -64,7 +63,6 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
@Override
public void handleResult(Result rawResult) {
//Log.d(TAG, rawResult.getBarcodeFormat().toString() + "/" + rawResult.getText());
if ((rawResult.getBarcodeFormat() == BarcodeFormat.QR_CODE) &&
(rawResult.getText().startsWith(QR_SCHEME))) {
if (activityCallback.onAddressScanned(rawResult.getText())) {
@@ -101,7 +99,6 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
@Override
public void onAttach(Context context) {
super.onAttach(context);
//Log.d(TAG, "attaching scan");
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
} else {

File diff suppressed because it is too large Load Diff

View File

@@ -16,19 +16,22 @@
package com.m2049r.xmrwallet;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.m2049r.xmrwallet.layout.Toolbar;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet;
@@ -64,14 +67,13 @@ public class TxFragment extends Fragment {
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);
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
tvTxTimestamp = (TextView) view.findViewById(R.id.tvTxTimestamp);
tvTxId = (TextView) view.findViewById(R.id.tvTxId);
@@ -83,18 +85,10 @@ public class TxFragment extends Fragment {
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) {
@@ -119,28 +113,35 @@ public class TxFragment extends Fragment {
}
}
void copyToClipboard() {
void shareTxInfo() {
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(getString(R.string.tx_timestamp)).append(":\n");
sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n\n");
sb.append(getString(R.string.tx_amount)).append(":\n");
sb.append((info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-"));
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(": ");
sb.append(getString(R.string.tx_fee)).append(":\n");
sb.append(Wallet.getDisplayAmount(info.fee)).append("\n\n");
sb.append(getString(R.string.tx_notes)).append(":\n");
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(oneLineNotes.isEmpty() ? "-" : oneLineNotes).append("\n\n");
sb.append(getString(R.string.tx_destination)).append(":\n");
sb.append(tvDestination.getText()).append("\n\n");
sb.append(getString(R.string.tx_paymentId)).append(":\n");
sb.append(info.paymentId).append("\n\n");
sb.append(getString(R.string.tx_id)).append(":\n");
sb.append(info.hash).append("\n");
sb.append(getString(R.string.tx_key)).append(":\n");
sb.append(info.txKey.isEmpty() ? "-" : info.txKey).append("\n\n");
sb.append(getString(R.string.tx_blockheight)).append(":\n");
if (info.isFailed) {
sb.append(getString(R.string.tx_failed)).append("\n");
} else if (info.isPending) {
@@ -148,7 +149,9 @@ public class TxFragment extends Fragment {
} else {
sb.append(info.blockheight).append("\n");
}
sb.append(getString(R.string.tx_transfers)).append(": ");
sb.append("\n");
sb.append(getString(R.string.tx_transfers)).append(":\n");
if (info.transfers != null) {
boolean comma = false;
for (Transfer transfer : info.transfers) {
@@ -163,12 +166,14 @@ public class TxFragment extends Fragment {
} 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());
sb.append("\n\n");
//Helper.clipBoardCopy(getActivity(), getString(R.string.tx_copy_label), sb.toString());
//Toast.makeText(getActivity(), getString(R.string.tx_copy_message), Toast.LENGTH_SHORT).show();
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, sb.toString());
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, null));
}
TransactionInfo info = null;
@@ -176,17 +181,24 @@ public class TxFragment extends Fragment {
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 setTxColour(int clr) {
tvTxAmount.setTextColor(clr);
tvTxFee.setTextColor(clr);
}
private void show(TransactionInfo info) {
if (info.txKey == null) {
info.txKey = activityCallback.getTxKey(info.hash);
//Log.d(TAG, "TXKEY:" + info.txKey + ":");
}
loadNotes(info);
activityCallback.setSubtitle(getString(R.string.tx_title));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
tvTxId.setText(info.hash);
tvTxKey.setText(info.txKey.isEmpty() ? "-" : info.txKey);
@@ -199,8 +211,30 @@ public class TxFragment extends Fragment {
tvTxBlockheight.setText("" + info.blockheight);
}
String sign = (info.direction == TransactionInfo.Direction.Direction_In ? "+" : "-");
tvTxAmount.setText(sign + Wallet.getDisplayAmount(info.amount));
tvTxFee.setText(Wallet.getDisplayAmount(info.fee));
if ((info.fee > 0)) {
String fee = Wallet.getDisplayAmount(info.fee);
if (info.isPending) {
tvTxFee.setText(getString(R.string.tx_list_fee_pending, fee));
} else {
tvTxFee.setText(getString(R.string.tx_list_fee, fee));
}
} else {
tvTxFee.setText(null);
tvTxFee.setVisibility(View.GONE);
}
if (info.isFailed) {
tvTxAmount.setText(getString(R.string.tx_list_amount_failed, Wallet.getDisplayAmount(info.amount)));
tvTxFee.setText(getString(R.string.tx_list_failed_text));
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_failed));
} else if (info.isPending) {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_pending));
} else if (info.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_green));
} else {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_red));
}
Set<String> destinations = new HashSet<>();
StringBuffer sb = new StringBuffer();
StringBuffer dstSb = new StringBuffer();
@@ -232,10 +266,21 @@ public class TxFragment extends Fragment {
tvTxTransfers.setText(sb.toString());
tvDestination.setText(dstSb.toString());
this.info = info;
bCopy.setEnabled(true);
}
TxFragment.Listener activityCallback;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.tx_info_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
Listener activityCallback;
public interface Listener {
String getWalletAddress();
@@ -246,6 +291,10 @@ public class TxFragment extends Fragment {
void onSetNote(String txId, String notes);
void setToolbarButton(int type);
void setSubtitle(String subtitle);
}
@Override

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,161 @@
/**
* Copyright 2013 Adam Speakman, m2049r
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.dialog;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.WebView;
import android.widget.ProgressBar;
import com.m2049r.xmrwallet.R;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Based on LicensesFragment by Adam Speakman on 24/09/13.
* http://speakman.net.nz
*/
public class HelpFragment extends DialogFragment {
static final String TAG = "HelpFragment";
private static final String FRAGMENT_TAG = "com.m2049r.xmrwallet.dialog.HelpFragment";
private static final String HELP_ID = "HELP_ID";
private AsyncTask<Void, Void, String> loader;
public static HelpFragment newInstance(int helpResourceId) {
HelpFragment fragment = new HelpFragment();
Bundle bundle = new Bundle();
bundle.putInt(HELP_ID, helpResourceId);
fragment.setArguments(bundle);
return fragment;
}
/**
* @param fm A fragment manager instance used to display this LicensesFragment.
*/
public static void displayHelp(FragmentManager fm, int helpResourceId) {
FragmentTransaction ft = fm.beginTransaction();
Fragment prev = fm.findFragmentByTag(FRAGMENT_TAG);
if (prev != null) {
ft.remove(prev);
}
// Create and show the dialog.
DialogFragment newFragment = HelpFragment.newInstance(helpResourceId);
newFragment.show(ft, FRAGMENT_TAG);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
int helpId = 0;
Bundle arguments = getArguments();
if (arguments != null) {
helpId = arguments.getInt(HELP_ID);
}
if (helpId > 0)
loadHelp(helpId);
}
@Override
public void onDestroy() {
super.onDestroy();
if (loader != null) {
loader.cancel(true);
}
}
private WebView webView;
private ProgressBar progress;
@SuppressLint("InflateParams")
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View content = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_help, null);
webView = (WebView) content.findViewById(R.id.helpFragmentWebView);
progress = (ProgressBar) content.findViewById(R.id.helpFragmentProgress);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(content);
builder.setNegativeButton(R.string.about_close,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
return builder.create();
}
private void loadHelp(final int helpResourceId) {
// Load asynchronously in case of a very large file.
loader = new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... params) {
InputStream rawResource = getActivity().getResources().openRawResource(helpResourceId);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(rawResource));
String line;
StringBuilder sb = new StringBuilder();
try {
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
bufferedReader.close();
} catch (IOException ex) {
Log.e(TAG, ex.getLocalizedMessage());
}
return sb.toString();
}
@Override
protected void onPostExecute(String licensesBody) {
super.onPostExecute(licensesBody);
if (getActivity() == null || isCancelled()) {
return;
}
progress.setVisibility(View.INVISIBLE);
webView.setVisibility(View.VISIBLE);
webView.loadDataWithBaseURL(null, licensesBody, "text/html", "utf-8", null);
loader = null;
}
}.execute();
}
}

View File

@@ -14,12 +14,7 @@
* limitations under the License.
*/
package com.m2049r.xmrwallet.license;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
package com.m2049r.xmrwallet.dialog;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
@@ -27,11 +22,11 @@ import android.app.Dialog;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Bundle;
//TODO If you don't support Android 2.x, you should use the non-support version!
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.webkit.WebView;
@@ -41,18 +36,23 @@ import android.widget.TextView;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Created by Adam Speakman on 24/09/13.
* http://speakman.net.nz
*/
public class LicensesFragment extends DialogFragment {
static final String TAG = "LicensesFragment";
int versionCode = BuildConfig.VERSION_CODE;
String versionName = BuildConfig.VERSION_NAME;
private AsyncTask<Void, Void, String> mLicenseLoader;
private static final String FRAGMENT_TAG = "nz.net.speakman.androidlicensespage.LicensesFragment";
private static final String KEY_SHOW_CLOSE_BUTTON = "keyShowCloseButton";
private static final String FRAGMENT_TAG = "com.m2049r.xmrwalelt.dialog.LicensesFragment";
/**
* Creates a new instance of LicensesFragment with no Close button.
@@ -63,22 +63,6 @@ public class LicensesFragment extends DialogFragment {
return new LicensesFragment();
}
/**
* Creates a new instance of LicensesFragment with an optional Close button.
*
* @param showCloseButton Whether to show a Close button at the bottom of the dialog.
* @return A new licenses fragment.
*/
public static LicensesFragment newInstance(boolean showCloseButton) {
LicensesFragment fragment = new LicensesFragment();
Bundle bundle = new Bundle();
bundle.putBoolean(KEY_SHOW_CLOSE_BUTTON, showCloseButton);
fragment.setArguments(bundle);
return fragment;
}
/**
* Builds and displays a licenses fragment with no Close button. Requires
* "/res/raw/licenses.html" and "/res/layout/licenses_fragment.xml" to be
@@ -99,27 +83,6 @@ public class LicensesFragment extends DialogFragment {
newFragment.show(ft, FRAGMENT_TAG);
}
/**
* Builds and displays a licenses fragment with or without a Close button.
* Requires "/res/raw/licenses.html" and "/res/layout/licenses_fragment.xml"
* to be present.
*
* @param fm A fragment manager instance used to display this LicensesFragment.
* @param showCloseButton Whether to show a Close button at the bottom of the dialog.
*/
public static void displayLicensesFragment(FragmentManager fm, boolean showCloseButton) {
FragmentTransaction ft = fm.beginTransaction();
Fragment prev = fm.findFragmentByTag(FRAGMENT_TAG);
if (prev != null) {
ft.remove(prev);
}
ft.addToBackStack(null);
// Create and show the dialog.
DialogFragment newFragment = LicensesFragment.newInstance(showCloseButton);
newFragment.show(ft, FRAGMENT_TAG);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@@ -140,30 +103,22 @@ public class LicensesFragment extends DialogFragment {
@SuppressLint("InflateParams")
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
View content = LayoutInflater.from(getActivity()).inflate(R.layout.licenses_fragment, null);
View content = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_licenses, null);
mWebView = (WebView) content.findViewById(R.id.licensesFragmentWebView);
mIndeterminateProgress = (ProgressBar) content.findViewById(R.id.licensesFragmentIndeterminateProgress);
boolean showCloseButton = false;
Bundle arguments = getArguments();
if (arguments != null) {
showCloseButton = arguments.getBoolean(KEY_SHOW_CLOSE_BUTTON);
}
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
TextView text = (TextView) content.findViewById(R.id.text);
TextView text = (TextView) content.findViewById(R.id.licensesFragmentText);
text.setText(getString(R.string.about_text, versionName, versionCode));
builder.setView(content);
if (showCloseButton) {
builder.setNegativeButton(R.string.about_close,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
}
builder.setNegativeButton(R.string.about_close,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
return builder.create();
}
@@ -186,8 +141,8 @@ public class LicensesFragment extends DialogFragment {
sb.append("\n");
}
bufferedReader.close();
} catch (IOException e) {
// TODO You may want to include some logging here.
} catch (IOException ex) {
Log.e(TAG, ex.getLocalizedMessage());
}
return sb.toString();

View File

@@ -0,0 +1,159 @@
/*
* 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.layout;
import android.os.AsyncTask;
import android.util.Log;
import com.m2049r.xmrwallet.util.Helper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.ref.WeakReference;
public class AsyncExchangeRate extends AsyncTask<String, Void, Boolean> {
static final String TAG = "AsyncExchangeRate";
static final long TIME_REFRESH_INTERVAL = 60000; // refresh exchange rate max every minute
public interface Listener {
void exchangeFailed();
// callback from AsyncExchangeRate when we have a rate
void exchange(String currencyA, String currencyB, double rate);
}
static long RateTime = 0;
static double Rate = 0;
static String Fiat = null;
private final WeakReference<Listener> exchangeViewRef;
public AsyncExchangeRate(Listener exchangeView) {
super();
exchangeViewRef = new WeakReference<>(exchangeView);
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
boolean inverse = false;
String currencyA = null;
String currencyB = null;
@Override
protected Boolean doInBackground(String... params) {
if (params.length != 2) return false;
Log.d(TAG, "Getting " + params[0]);
currencyA = params[0];
currencyB = params[1];
String fiat = null;
if (currencyA.equals("XMR")) {
fiat = currencyB;
inverse = false;
}
if (currencyB.equals("XMR")) {
fiat = currencyA;
inverse = true;
}
if (currencyA.equals(currencyB)) {
Fiat = null;
Rate = 1;
RateTime = System.currentTimeMillis();
return true;
}
if (fiat == null) {
Fiat = null;
Rate = 0;
RateTime = 0;
return false;
}
if (!fiat.equals(Fiat)) { // new currency - reset all
Fiat = fiat;
Rate = 0;
RateTime = 0;
}
if (System.currentTimeMillis() > RateTime + TIME_REFRESH_INTERVAL) {
Log.d(TAG, "Fetching " + Fiat);
String closePrice = getExchangeRate(Fiat);
if (closePrice != null) {
try {
Rate = Double.parseDouble(closePrice);
RateTime = System.currentTimeMillis();
return true;
} catch (NumberFormatException ex) {
Rate = 0;
Log.e(TAG, ex.getLocalizedMessage());
return false;
}
} else {
Rate = 0;
Log.e(TAG, "exchange url failed");
return false;
}
}
return true; // no change but still valid
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
Listener exchangeView = exchangeViewRef.get();
if (result) {
Log.d(TAG, "yay! = " + Rate);
if (exchangeView != null) {
exchangeView.exchange(currencyA, currencyB, inverse ? (1 / Rate) : Rate);
}
} else {
Log.d(TAG, "nay!");
if (exchangeView != null) {
exchangeView.exchangeFailed();
}
}
}
// "https://api.kraken.com/0/public/Ticker?pair=XMREUR"
String getExchangeRate(String fiat) {
String jsonResponse =
Helper.getUrl("https://api.kraken.com/0/public/Ticker?pair=XMR" + fiat);
if (jsonResponse == null) return null;
try {
JSONObject response = new JSONObject(jsonResponse);
JSONArray errors = response.getJSONArray("error");
Log.e(TAG, "errors=" + errors.toString());
if (errors.length() == 0) {
JSONObject result = response.getJSONObject("result");
JSONObject pair = result.getJSONObject("XXMRZ" + fiat);
JSONArray close = pair.getJSONArray("c");
String closePrice = close.getString(0);
Log.d(TAG, "closePrice=" + closePrice);
return closePrice;
}
} catch (JSONException ex) {
Log.e(TAG, ex.getLocalizedMessage());
}
return null;
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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.
*/
// based on from https://stackoverflow.com/a/45325876 (which did not work for me)
package com.m2049r.xmrwallet.layout;
import android.content.Context;
import android.support.design.widget.TextInputLayout;
import android.util.AttributeSet;
import android.widget.EditText;
public class CTextInputLayout extends TextInputLayout {
public CTextInputLayout(Context context) {
super(context);
}
public CTextInputLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public int getBaseline() {
EditText editText = getEditText();
return editText.getBaseline() - (getMeasuredHeight() - editText.getMeasuredHeight());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
/*
* 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.
*/
// based on https://code.tutsplus.com/tutorials/creating-compound-views-on-android--cms-22889
package com.m2049r.xmrwallet.layout;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
public class Toolbar extends android.support.v7.widget.Toolbar {
static final String TAG = "Toolbar";
public interface OnButtonListener {
void onButton(int type);
}
OnButtonListener onButtonListener;
public void setOnButtonListener(OnButtonListener listener) {
onButtonListener = listener;
}
ImageView toolbarImage;
TextView toolbarTitle;
TextView toolbarSubtitle;
Button bDonate;
public Toolbar(Context context) {
super(context);
initializeViews(context);
}
public Toolbar(Context context, AttributeSet attrs) {
super(context, attrs);
initializeViews(context);
}
public Toolbar(Context context,
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initializeViews(context);
}
/**
* Inflates the views in the layout.
*
* @param context the current context for the view.
*/
private void initializeViews(Context context) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_toolbar, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
toolbarImage = (ImageView) findViewById(R.id.toolbarImage);
toolbarTitle = (TextView) findViewById(R.id.toolbarTitle);
toolbarSubtitle = (TextView) findViewById(R.id.toolbarSubtitle);
bDonate = (Button) findViewById(R.id.bDonate);
bDonate.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (onButtonListener != null) {
onButtonListener.onButton(buttonType);
}
}
});
}
public void setTitle(String title, String subtitle) {
setTitle(title);
setSubtitle(subtitle);
}
public void setTitle(String title) {
toolbarTitle.setText(title);
if (title != null) {
toolbarImage.setVisibility(View.INVISIBLE);
toolbarTitle.setVisibility(View.VISIBLE);
} else {
toolbarImage.setVisibility(View.VISIBLE);
toolbarTitle.setVisibility(View.INVISIBLE);
}
}
public final static int BUTTON_NONE = 0;
public final static int BUTTON_BACK = 1;
public final static int BUTTON_CLOSE = 2;
public final static int BUTTON_DONATE = 3;
int buttonType = BUTTON_DONATE;
public void setButton(int type) {
switch (type) {
case BUTTON_BACK:
Log.d(TAG, "BUTTON_BACK");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_arrow_back_white_24dp, 0, 0, 0);
bDonate.setText(null);
bDonate.setVisibility(View.VISIBLE);
break;
case BUTTON_CLOSE:
Log.d(TAG, "BUTTON_CLOSE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_close_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_close);
bDonate.setVisibility(View.VISIBLE);
break;
case BUTTON_DONATE:
Log.d(TAG, "BUTTON_DONATE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_favorite_white_24dp, 0, 0, 0);
bDonate.setText(R.string.label_donate);
bDonate.setVisibility(View.VISIBLE);
break;
case BUTTON_NONE:
default:
Log.d(TAG, "BUTTON_NONE");
bDonate.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
bDonate.setText(null);
bDonate.setVisibility(View.INVISIBLE);
}
buttonType = type;
}
public void setSubtitle(String subtitle) {
toolbarSubtitle.setText(subtitle);
if (subtitle != null) {
toolbarSubtitle.setVisibility(View.VISIBLE);
} else {
toolbarSubtitle.setVisibility(View.INVISIBLE);
}
}
}

View File

@@ -17,7 +17,6 @@
package com.m2049r.xmrwallet.layout;
import android.content.Context;
import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
@@ -28,13 +27,12 @@ import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
@@ -69,7 +67,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
outboundColour = ContextCompat.getColor(context, R.color.tx_red);
pendingColour = ContextCompat.getColor(context, R.color.tx_pending);
failedColour = ContextCompat.getColor(context, R.color.tx_failed);
this.infoItems = new ArrayList<>();
infoItems = new ArrayList<>();
this.listener = listener;
Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone(); //get the local time zone.
@@ -79,7 +77,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.transaction_item, parent, false);
.inflate(R.layout.item_transaction, parent, false);
return new ViewHolder(view);
}
@@ -99,22 +97,8 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
this.infoItems.clear();
if (data != null) {
Log.d(TAG, "setInfos " + data.size());
// sort by block height
Collections.sort(data, new Comparator<TransactionInfo>() {
@Override
public int compare(TransactionInfo o1, TransactionInfo o2) {
long b1 = o1.timestamp;
long b2 = o2.timestamp;
if (b1 > b2) {
return -1;
} else if (b1 < b2) {
return 1;
} else {
return o1.hash.compareTo(o2.hash);
}
}
});
this.infoItems.addAll(data);
infoItems.addAll(data);
Collections.sort(infoItems);
} else {
Log.d(TAG, "setInfos null");
}
@@ -147,36 +131,41 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
void bind(int position) {
this.infoItem = infoItems.get(position);
String displayAmount = Wallet.getDisplayAmount(infoItem.amount);
// TODO fix this with i8n code but cryptonote::print_money always uses '.' for decimal point
String amount = displayAmount.substring(0, displayAmount.length() - (12 - 5));
this.tvAmount.setText(amount);
long realAmount = infoItem.amount;
if (infoItem.isPending) {
realAmount = realAmount - infoItem.fee;
}
String displayAmount = Helper.getDisplayAmount(realAmount, Helper.DISPLAY_DIGITS_INFO);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_negative, displayAmount));
} else {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_positive, displayAmount));
}
if ((infoItem.fee > 0)) {
String feeAmount = Wallet.getDisplayAmount(infoItem.fee);
String fee = feeAmount.substring(0, feeAmount.length() - (12 - 5));
if (infoItem.isPending) {
this.tvFee.setText(context.getString(R.string.tx_list_fee_pending, fee));
} else {
this.tvFee.setText(context.getString(R.string.tx_list_fee, fee));
}
String fee = Helper.getDisplayAmount(infoItem.fee, 5);
this.tvFee.setText(context.getString(R.string.tx_list_fee, fee));
} else {
this.tvFee.setText("");
}
if (infoItem.isFailed) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, amount));
this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount));
this.tvFee.setText(context.getString(R.string.tx_list_failed_text));
setTxColour(failedColour);
} else if (infoItem.isPending) {
setTxColour(pendingColour);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_negative, amount));
}
} else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(inboundColour);
} else {
setTxColour(outboundColour);
}
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ? "" : infoItem.paymentId);
if ((infoItem.notes == null) || (infoItem.notes.isEmpty())) {
this.tvPaymentId.setText(infoItem.paymentId.equals("0000000000000000") ? "" : infoItem.paymentId);
} else {
this.tvPaymentId.setText(infoItem.notes);
}
this.tvDateTime.setText(getDateTime(infoItem.timestamp));
itemView.setOnClickListener(this);

View File

@@ -0,0 +1,157 @@
/*
* 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.layout;
import android.content.Context;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.WalletManager;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.ViewHolder> {
private static final String TAG = "WalletInfoAdapter";
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public interface OnInteractionListener {
void onInteraction(View view, WalletManager.WalletInfo item);
boolean onContextInteraction(MenuItem item, WalletManager.WalletInfo infoItem);
}
private final List<WalletManager.WalletInfo> infoItems;
private final OnInteractionListener listener;
Context context;
public WalletInfoAdapter(Context context, OnInteractionListener listener) {
this.context = context;
this.infoItems = new ArrayList<>();
this.listener = listener;
Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone(); //get the local time zone.
DATETIME_FORMATTER.setTimeZone(tz);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_wallet, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.bind(position);
}
@Override
public int getItemCount() {
return infoItems.size();
}
public WalletManager.WalletInfo getItem(int position) {
return infoItems.get(position);
}
public void setInfos(List<WalletManager.WalletInfo> data) {
// TODO do stuff with data so we can really recycle elements (i.e. add only new tx)
// as the WalletInfo items are always recreated, we cannot recycle
infoItems.clear();
if (data != null) {
Log.d(TAG, "setInfos " + data.size());
infoItems.addAll(data);
Collections.sort(infoItems);
} else {
Log.d(TAG, "setInfos null");
}
notifyDataSetChanged();
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView tvName;
final TextView tvAddress;
final ImageButton ibOptions;
WalletManager.WalletInfo infoItem;
ViewHolder(View itemView) {
super(itemView);
tvName = (TextView) itemView.findViewById(R.id.tvName);
tvAddress = (TextView) itemView.findViewById(R.id.tvAddress);
ibOptions = (ImageButton) itemView.findViewById(R.id.ibOptions);
ibOptions.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//creating a popup menu
PopupMenu popup = new PopupMenu(context, ibOptions);
//inflating menu from xml resource
popup.inflate(R.menu.list_context_menu);
//adding click listener
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
if (listener != null) {
return listener.onContextInteraction(item, infoItem);
}
return false;
}
});
//displaying the popup
popup.show();
}
});
itemView.setOnClickListener(this);
}
private String getDateTime(long time) {
return DATETIME_FORMATTER.format(new Date(time * 1000));
}
void bind(int position) {
infoItem = infoItems.get(position);
tvName.setText(infoItem.name);
tvAddress.setText(infoItem.address.substring(0, 16) + "...");
}
@Override
public void onClick(View view) {
if (listener != null) {
int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
listener.onInteraction(view, infoItems.get(position));
}
}
}
}
}

View File

@@ -30,6 +30,12 @@ public class TransactionHistory {
this.handle = handle;
}
public void loadNotes(Wallet wallet) {
for (TransactionInfo info : transactions) {
info.notes = wallet.getUserNote(info.hash);
}
}
public native int getCount();
//private native long getTransactionByIndexJ(int i);
@@ -42,6 +48,11 @@ public class TransactionHistory {
private List<TransactionInfo> transactions = new ArrayList<>();
public void refreshWithNotes(Wallet wallet) {
refresh();
loadNotes(wallet);
}
public void refresh() {
transactions = refreshJ();
}

View File

@@ -25,7 +25,7 @@ import java.util.Random;
// 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 implements Parcelable {
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
static final String TAG = "TransactionInfo";
public enum Direction {
@@ -92,7 +92,6 @@ public class TransactionInfo implements Parcelable {
this.confirmations = confirmations;
this.transfers = transfers;
}
Random rnd = new Random();
public String toString() {
return direction + "@" + blockheight + " " + amount;
@@ -146,4 +145,17 @@ public class TransactionInfo implements Parcelable {
return 0;
}
@Override
public int compareTo(TransactionInfo another) {
long b1 = this.timestamp;
long b2 = another.timestamp;
if (b1 > b2) {
return -1;
} else if (b1 < b2) {
return 1;
} else {
return this.hash.compareTo(another.hash);
}
}
}

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