1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-03 08:23:04 +02:00

Compare commits

...

44 Commits

Author SHA1 Message Date
m2049r
807d217aac Merge pull request #16 from m2049r/feature_txrecipient
show destinations in tx view
2017-08-22 13:10:14 +02:00
m2049r
0421bcfac3 show destination in tx view 2017-08-22 13:02:31 +02:00
m2049r
b6c4e06fda Merge pull request #15 from m2049r/feature_spend_redo
enable dispose of prepared transaction
2017-08-22 11:10:19 +02:00
m2049r
3ee8343074 enable dispose of prepared transaction 2017-08-22 11:05:43 +02:00
m2049r
110621824a Merge pull request #14 from m2049r/hotfix_priority
passing of transaction priority fixed
2017-08-22 10:56:07 +02:00
m2049r
7194580fbe passing of transaction priority fixed 2017-08-22 10:54:16 +02:00
m2049r
48dcd37740 Update build.gradle 2017-08-22 00:19:53 +02:00
m2049r
8e619756b9 Merge pull request #13 from m2049r/feature_service_notification
start WalletService as foreground service
2017-08-21 17:33:08 +02:00
m2049r
6c6b47db7d Merge pull request #12 from m2049r/bugfix
don't show Send Button on mainnet (instead of crashing when clicked)
2017-08-21 17:31:45 +02:00
m2049r
a126844272 start WalletService as foreground service preventing it from being killed by the system 2017-08-21 15:58:22 +02:00
m2049r
9b3086ff2e don't show Send Button on mainnet (instead of crashing when clicked) 2017-08-21 10:14:19 +02:00
m2049r
89d1842d59 Update README.md 2017-08-21 00:22:57 +02:00
m2049r
d44067e952 Merge pull request #11 from m2049r/feature_send
doc
2017-08-20 22:47:20 +02:00
m2049r
0370eaa619 doc 2017-08-20 22:45:46 +02:00
m2049r
35c393495a Merge pull request #10 from m2049r/feature_send
Sending
2017-08-20 22:42:22 +02:00
m2049r
f19618e017 docs 2017-08-20 22:35:42 +02:00
m2049r
3a68802ae7 deal with pending transactions 2017-08-20 22:35:31 +02:00
m2049r
344eeb7a3b removed debugging code + docs + some bugfixing 2017-08-20 22:35:03 +02:00
m2049r
4ad58684b4 failsafe sending in mainnet
- die ungracefully with an exception because this should be possible anyway
2017-08-20 22:35:03 +02:00
m2049r
30e35a895d cleanup + docs 2017-08-20 22:34:36 +02:00
m2049r
37414be4bf tx notes added + tx details + tweaks 2017-08-20 16:24:35 +02:00
m2049r
a37ca4c0e5 tweaks + sweep 2017-08-19 18:59:38 +02:00
m2049r
408b1a68d0 tx key + tweaks 2017-08-19 16:20:10 +02:00
m2049r
40da44222e sending done + added Transfers + tweaks 2017-08-19 13:44:04 +02:00
m2049r
b0efdca928 send gui + first successful send transaction! 2017-08-19 13:43:29 +02:00
m2049r
d61198b47f Merge pull request #9 from m2049r/feature_walletinfo
Wallet Details on long press in wallet list
2017-08-18 09:35:02 +02:00
m2049r
62433f6e10 tweaks & fixed store() error, refactoring 2017-08-18 02:46:56 +02:00
m2049r
c9ae39508f details view works mostly (seed)
need to prevent opening wallet while preparing details
new wallets sont store the cache ?! (except watch only)
2017-08-18 00:18:57 +02:00
m2049r
44836a24bb tweaks 2017-08-18 00:18:43 +02:00
m2049r
80e60bd0c8 Merge pull request #8 from m2049r/feature_gen
Wallet Creation & Recovery
2017-08-17 14:17:13 +02:00
m2049r
9f38b957e1 lots of minor fixes & tweaks. do not copy cache.
the cache file is corrupt after recovery (except watch only).
2017-08-17 14:13:26 +02:00
m2049r
032aa24ab5 cleanup & AppCompatActivity & setTitle 2017-08-16 22:22:41 +02:00
m2049r
afc45e1cbc UI tweaks 2017-08-16 21:28:00 +02:00
m2049r
bd598deddd separate wallet generation & confirmation
all methods of recovery & creation implemented
2017-08-16 21:28:00 +02:00
m2049r
952fb3a7f1 deal with user closing fragment while doing wallet stuff 2017-08-16 21:28:00 +02:00
m2049r
fb62074d20 added app.iml to gitignore and updates android studio 2017-08-16 21:27:36 +02:00
m2049r
56132e26ed tweaks - mostly keyboard & UI 2017-08-16 12:01:32 +02:00
m2049r
3239bdeb33 removed default localhost daemons 2017-08-16 00:49:48 +02:00
m2049r
142885821e Generate Wallet command added to wallet list 2017-08-16 00:28:53 +02:00
m2049r
34941c599a Generate Wallet Fragment added 2017-08-15 23:59:41 +02:00
m2049r
58c8f1896c Merge pull request #7 from m2049r/fragment
Fragmented Activites
2017-08-15 12:17:56 +02:00
m2049r
6611539491 some cleaning up 2017-08-15 12:13:58 +02:00
m2049r
69729e5257 LoginActivity now fragmented 2017-08-15 12:01:53 +02:00
m2049r
535158c5e9 refactored WalletActivity to use fragments 2017-08-15 09:43:19 +02:00
39 changed files with 4398 additions and 1031 deletions

1
.idea/.gitignore generated vendored
View File

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

View File

@@ -3,39 +3,35 @@ Another Android Monero Wallet
### QUICKSTART
- Download APK (Release) and install it
- Copy over synced wallet (all three files) onto sdcard in directory Monerujo (created first time app is started)
- Start app (again)
- Run the App and select "Generate Wallet" to create a new wallet or recover a wallet
- 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)
### Disclaimer
This is my first serious Android App.
You may loose all your Moneroj if you use this App.
### Random Notes
- 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 use is checking incoming/outgoing transactions
- works in testnet & mainnet (please use your own daemons)
- takes forever to sync on mainnet (even with own daemon) - due to 32-bit architecture
- ~~currently only use is checking incoming/outgoing transactions~~
- works in testnet & mainnet
- takes forever to sync 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
- 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/
### 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)
- review visibility of methods/classes
- sensible error dialogs (e.g. when no write permissions granted) instead of just crashing on purpose
- spend monero - not so difficult with wallet api
- more sensible error dialogs ~~(e.g. when no write permissions granted) instead of just crashing on purpose~~
- check licenses of included libraries; License Dialog
- ~~provide detailed build instructions for third party binaries~~
- ~~sensible loading/saving progress bars instead of just freezing up~~
- ~~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~~
- ~~make it pretty~~ (decided to go with "form follows function")
- ~~spend monero - not so difficult with wallet api~~
### Issues
- ~~screen rotation crashes the app~~
- ~~turning the display off/on during sync stops sync~~
- Pending incoming transactions disappear after reload
### HOW TO BUILD
No need to build. Binaries are included:
@@ -47,3 +43,6 @@ No need to build. Binaries are included:
If you want to build them yourself (recommended) check out [the instructions](doc/BUILDING-external-libs.md)
Then, fire up Android Studio and build the APK.
### Donations
4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk

1
app/.gitignore vendored
View File

@@ -1,2 +1,3 @@
.externalNativeBuild
build
app.iml

View File

@@ -1,147 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":app" />
</configuration>
</facet>
<facet type="native-android-gradle" name="Native-Android-Gradle">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<afterSyncTasks>
<task>generateDebugSources</task>
</afterSyncTasks>
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/test/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/cpp" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/testDebug/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/cmake" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-runtime-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-safeguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental-verifier" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-resources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/instant-run-support" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jniLibs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/reload-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/restart-dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/shaders" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/split-apk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/transforms" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 25 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" scope="TEST" name="runner-0.5" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="espresso-idling-resource-2.2.2" level="project" />
<orderEntry type="library" exported="" name="constraint-layout-1.0.2" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-library-1.3" level="project" />
<orderEntry type="library" exported="" name="transition-25.3.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-integration-1.3" level="project" />
<orderEntry type="library" exported="" name="design-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-core-ui-25.3.1" level="project" />
<orderEntry type="library" exported="" name="cardview-v7-25.3.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="jsr305-2.0.1" level="project" />
<orderEntry type="library" exported="" name="support-core-utils-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-fragment-25.3.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="espresso-core-2.2.2" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="exposed-instrumentation-api-publish-0.5" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="rules-0.5" level="project" />
<orderEntry type="library" exported="" name="constraint-layout-solver-1.0.2" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="javax.annotation-api-1.2" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="javax.inject-1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="javawriter-2.1.1" level="project" />
<orderEntry type="library" exported="" name="support-v4-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-media-compat-25.3.1" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" exported="" name="recyclerview-v7-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-annotations-25.3.1" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-vector-drawable-25.3.1" level="project" />
<orderEntry type="library" exported="" name="support-compat-25.3.1" level="project" />
<orderEntry type="library" exported="" name="animated-vector-drawable-25.3.1" level="project" />
</component>
</module>

View File

@@ -7,8 +7,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 25
versionCode 3
versionName "0.3.0"
versionCode 6
versionName "0.4.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {

View File

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

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,166 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.MoneroHandlerThread;
import java.io.File;
public class GenerateReviewFragment extends Fragment {
static final String TAG = "GenerateReviewFragment";
static final public String VIEW_DETAILS = "details";
static final public String VIEW_ACCEPT = "accept";
static final public String VIEW_WALLET = "wallet";
ProgressBar pbProgress;
TextView tvWalletName;
TextView tvWalletPassword;
TextView tvWalletAddress;
TextView tvWalletMnemonic;
TextView tvWalletViewKey;
TextView tvWalletSpendKey;
Button bAccept;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.gen_review_fragment, container, false);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
tvWalletName = (TextView) view.findViewById(R.id.tvWalletName);
tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword);
tvWalletAddress = (TextView) view.findViewById(R.id.tvWalletAddress);
tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey);
tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey);
tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
bAccept = (Button) view.findViewById(R.id.bAccept);
boolean testnet = WalletManager.getInstance().isTestNet();
tvWalletMnemonic.setTextIsSelectable(testnet);
bAccept.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
acceptWallet();
}
});
showProgress();
Bundle b = getArguments();
String type = b.getString("type");
if (!type.equals(VIEW_WALLET)) {
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;
}
private void acceptWallet() {
String name = tvWalletName.getText().toString();
String password = tvWalletPassword.getText().toString();
bAccept.setEnabled(false);
acceptCallback.onAccept(name, password);
}
private void show(final String walletPath, final String password, final String type) {
new Thread(null,
new Runnable() {
@Override
public void run() {
final Wallet wallet = WalletManager.getInstance().openWallet(walletPath, password);
getActivity().runOnUiThread(new Runnable() {
public void run() {
show(wallet, password, type);
wallet.close();
}
});
}
}
, "DetailsReview", MoneroHandlerThread.THREAD_STACK_SIZE).start();
}
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 {
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");
}
}
public void showProgress() {
pbProgress.setIndeterminate(true);
pbProgress.setVisibility(View.VISIBLE);
}
public void hideProgress() {
pbProgress.setVisibility(View.INVISIBLE);
}
}

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

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,240 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.app.Fragment;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.DividerItemDecoration;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import java.text.NumberFormat;
import java.util.List;
public class WalletFragment extends Fragment implements TransactionInfoAdapter.OnInteractionListener {
private static final String TAG = "WalletFragment";
private TransactionInfoAdapter adapter;
private NumberFormat formatter = NumberFormat.getInstance();
TextView tvBalance;
TextView tvUnlockedBalance;
TextView tvBlockHeightProgress;
TextView tvConnectionStatus;
LinearLayout llProgress;
TextView tvProgress;
ProgressBar pbProgress;
Button bSend;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.wallet_fragment, container, false);
tvProgress = (TextView) view.findViewById(R.id.tvProgress);
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
llProgress = (LinearLayout) view.findViewById(R.id.llProgress);
tvBalance = (TextView) view.findViewById(R.id.tvBalance);
tvUnlockedBalance = (TextView) view.findViewById(R.id.tvUnlockedBalance);
tvBlockHeightProgress = (TextView) view.findViewById(R.id.tvBlockHeightProgress);
tvConnectionStatus = (TextView) view.findViewById(R.id.tvConnectionStatus);
bSend = (Button) view.findViewById(R.id.bSend);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
RecyclerView.ItemDecoration itemDecoration = new
DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(itemDecoration);
this.adapter = new TransactionInfoAdapter(this);
recyclerView.setAdapter(adapter);
bSend.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
activityCallback.onSendRequest();
}
});
if (activityCallback.isSynced()) {
onSynced();
}
// activityCallback.setTitle(getString(R.string.status_wallet_loading));
activityCallback.forceUpdate();
return view;
}
// Callbacks from TransactionInfoAdapter
@Override
public void onInteraction(final View view, final TransactionInfo infoItem) {
activityCallback.onTxDetailsRequest(infoItem);
}
// called from activity
public void onRefreshed(final Wallet wallet, final boolean full) {
Log.d(TAG, "onRefreshed()");
if (full) {
List<TransactionInfo> list = wallet.getHistory().getAll();
adapter.setInfos(list);
adapter.notifyDataSetChanged();
}
updateStatus(wallet);
}
public void onSynced() {
if (!activityCallback.isWatchOnly() && WalletManager.getInstance().isTestNet()) {
bSend.setVisibility(View.VISIBLE);
bSend.setEnabled(true);
}
}
public void onProgress(final String text) {
if (text != null) {
tvProgress.setText(text);
showProgress();
} else {
hideProgress();
tvProgress.setText(getString(R.string.status_working));
onProgress(-1);
}
}
public void onProgress(final int n) {
if (n >= 0) {
pbProgress.setIndeterminate(false);
pbProgress.setProgress(n);
} else {
pbProgress.setIndeterminate(true);
}
}
public void showProgress() {
llProgress.setVisibility(View.VISIBLE);
}
public void hideProgress() {
llProgress.setVisibility(View.GONE);
}
String setActivityTitle(Wallet wallet) {
if (wallet == null) return null;
String shortName = wallet.getName();
if (shortName.length() > 16) {
shortName = shortName.substring(0, 14) + "...";
}
String title = "[" + wallet.getAddress().substring(0, 6) + "] "
+ shortName
+ (wallet.isWatchOnly() ? " " + getString(R.string.watchonly_label) : "");
activityCallback.setTitle(title);
Log.d(TAG, "wallet title is " + title);
return title;
}
private long firstBlock = 0;
private String walletTitle = null;
private void updateStatus(Wallet wallet) {
Log.d(TAG, "updateStatus()");
if (walletTitle == null) {
walletTitle = setActivityTitle(wallet);
onProgress(100); // of loading
}
tvBalance.setText(Wallet.getDisplayAmount(wallet.getBalance()));
tvUnlockedBalance.setText(Wallet.getDisplayAmount(wallet.getUnlockedBalance()));
String sync = "";
if (!activityCallback.hasBoundService())
throw new IllegalStateException("WalletService not bound.");
Wallet.ConnectionStatus daemonConnected = activityCallback.getConnectionStatus();
if (daemonConnected == Wallet.ConnectionStatus.ConnectionStatus_Connected) {
long daemonHeight = activityCallback.getDaemonHeight();
if (!wallet.isSynchronized()) {
long n = daemonHeight - wallet.getBlockChainHeight();
sync = formatter.format(n) + " " + getString(R.string.status_remaining);
if (firstBlock == 0) {
firstBlock = wallet.getBlockChainHeight();
}
int x = 100 - Math.round(100f * n / (1f * daemonHeight - firstBlock));
onProgress(getString(R.string.status_syncing) + " " + sync);
if (x == 0) x = -1;
onProgress(x);
} else {
sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight());
}
}
String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
tvBlockHeightProgress.setText(sync);
tvConnectionStatus.setText(net + " " + daemonConnected.toString().substring(17));
}
Listener activityCallback;
// Container Activity must implement this interface
public interface Listener {
boolean hasBoundService();
void forceUpdate();
Wallet.ConnectionStatus getConnectionStatus();
long getDaemonHeight(); //mBoundService.getDaemonHeight();
void setTitle(String title);
void onSendRequest();
void onTxDetailsRequest(TransactionInfo info);
boolean isSynced();
boolean isWatchOnly();
String getTxKey(String txId);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
} else {
throw new ClassCastException(context.toString()
+ " must implement Listener");
}
}
}

View File

@@ -40,11 +40,13 @@ import java.util.TimeZone;
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
private static final String TAG = "TransactionInfoAdapter";
private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
private static final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
private final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
private final SimpleDateFormat TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
static final int TX_RED = Color.rgb(255, 79, 65);
static final int TX_GREEN = Color.rgb(54, 176, 91);
static final int TX_PENDING = Color.rgb(72, 53, 176);
static final int TX_FAILED = Color.rgb(208, 0, 255);
public interface OnInteractionListener {
void onInteraction(View view, TransactionInfo item);
@@ -81,6 +83,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
public void setInfos(List<TransactionInfo> data) {
// 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();
if (data != null) {
Log.d(TAG, "setInfos " + data.size());
@@ -88,8 +91,15 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
Collections.sort(data, new Comparator<TransactionInfo>() {
@Override
public int compare(TransactionInfo o1, TransactionInfo o2) {
long b1 = o1.getBlockHeight();
long b2 = o2.getBlockHeight();
if ((o1.isPending) && (o2.isPending)) {
long b1 = o1.timestamp;
long b2 = o2.timestamp;
return (b1 > b2) ? -1 : (b1 < b2) ? 1 : 0;
}
if (o1.isPending) return -1;
if (o2.isPending) return 1;
long b1 = o1.blockheight;
long b2 = o2.blockheight;
return (b1 > b2) ? -1 : (b1 < b2) ? 1 : 0;
}
});
@@ -134,20 +144,28 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
void bind(int position) {
this.infoItem = infoItems.get(position);
String displayAmount = Wallet.getDisplayAmount(infoItem.getAmount());
// TODO fix this with i8n code
String displayAmount = Wallet.getDisplayAmount(infoItem.amount);
// TODO fix this with i8n code but cryptonote::print_money always uses '.' for decimal point
String amountParts[] = displayAmount.split("\\.");
// TODO what if there is no decimal point?
this.tvAmount.setText(amountParts[0]);
this.tvAmountDecimal.setText(amountParts[1]);
if (infoItem.getDirection() == TransactionInfo.Direction.Direction_In) {
if (infoItem.isPending) {
setTxColour(TX_PENDING);
if (infoItem.direction == TransactionInfo.Direction.Direction_Out) {
this.tvAmount.setText('-' + amountParts[0]);
}
} else if (infoItem.isFailed) {
this.tvAmount.setText('(' + amountParts[0]);
this.tvAmountDecimal.setText(amountParts[1] + ')');
setTxColour(TX_FAILED);
} else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(TX_GREEN);
} else {
setTxColour(TX_RED);
}
this.tvDate.setText(getDate(infoItem.getTimestamp()));
this.tvTime.setText(getTime(infoItem.getTimestamp()));
this.tvDate.setText(getDate(infoItem.timestamp));
this.tvTime.setText(getTime(infoItem.timestamp));
itemView.setOnClickListener(this);
}

View File

@@ -0,0 +1,88 @@
/*
* 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.model;
public class PendingTransaction {
static {
System.loadLibrary("monerujo");
}
public long handle;
PendingTransaction(long handle) {
this.handle = handle;
}
public enum Status {
Status_Ok,
Status_Error,
Status_Critical
}
public enum Priority {
Priority_Low(1),
Priority_Medium(2),
Priority_High(3),
Priority_Last(4);
public static Priority fromInteger(int n) {
switch (n) {
case 1:
return Priority_Low;
case 2:
return Priority_Medium;
case 3:
return Priority_High;
}
return null;
}
public int getValue() {
return value;
}
private int value;
Priority(int value) {
this.value = value;
}
}
public Status getStatus() {
return Status.values()[getStatusJ()];
}
public native int getStatusJ();
public native String getErrorString();
// commit transaction or save to file if filename is provided.
public native boolean commit(String filename, boolean overwrite);
public native long getAmount();
public native long getDust();
public native long getFee();
public native String getFirstTxId();
public native long getTxCount();
}

View File

@@ -16,6 +16,7 @@
package com.m2049r.xmrwallet.model;
import java.util.ArrayList;
import java.util.List;
public class TransactionHistory {
@@ -29,34 +30,22 @@ public class TransactionHistory {
this.handle = handle;
}
public TransactionInfo getTransaction(int i) {
long infoHandle = getTransactionByIndexJ(i);
return new TransactionInfo(infoHandle);
}
public TransactionInfo getTransaction(String id) {
long infoHandle = getTransactionByIdJ(id);
return new TransactionInfo(infoHandle);
}
/*
public List<TransactionInfo> getAll() {
List<Long> handles = getAllJ();
List<TransactionInfo> infoList = new ArrayList<TransactionInfo>();
for (Long handle : handles) {
infoList.add(new TransactionInfo(handle.longValue()));
}
return infoList;
}
*/
public native int getCount();
private native long getTransactionByIndexJ(int i);
//private native long getTransactionByIndexJ(int i);
private native long getTransactionByIdJ(String id);
//private native long getTransactionByIdJ(String id);
public native List<TransactionInfo> getAll();
public List<TransactionInfo> getAll() {
return transactions;
}
public native void refresh();
private List<TransactionInfo> transactions = new ArrayList<>();
public void refresh() {
transactions = refreshJ();
}
private native List<TransactionInfo> refreshJ();
}

View File

@@ -16,79 +16,134 @@
package com.m2049r.xmrwallet.model;
public class TransactionInfo {
static {
System.loadLibrary("monerujo");
}
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
public long handle;
import java.util.List;
import java.util.Random;
TransactionInfo(long handle) {
this.handle = handle;
}
// 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 {
static final String TAG = "TransactionInfo";
public enum Direction {
Direction_In,
Direction_Out
}
Direction_In(0),
Direction_Out(1);
public class Transfer {
long amount;
String address;
public Transfer(long amount, String address) {
this.amount = amount;
this.address = address;
public static Direction fromInteger(int n) {
switch (n) {
case 0:
return Direction_In;
case 1:
return Direction_Out;
}
return null;
}
public long getAmount() {
return amount;
public int getValue() {
return value;
}
public String getAddress() {
return address;
private int value;
Direction(int value) {
this.value = value;
}
}
public Direction direction;
public boolean isPending;
public boolean isFailed;
public long amount;
public long fee;
public long blockheight;
public String hash;
public long timestamp;
public String paymentId;
public long confirmations;
public List<Transfer> transfers;
public String txKey = null;
public String notes = null;
public TransactionInfo(
int direction,
boolean isPending,
boolean isFailed,
long amount,
long fee,
long blockheight,
String hash,
long timestamp,
String paymentId,
long confirmations,
List<Transfer> transfers) {
this.direction = Direction.values()[direction];
this.isPending = isPending;
this.isFailed = isFailed;
this.amount = amount;
this.fee = fee;
this.blockheight = blockheight;
this.hash = hash;
this.timestamp = timestamp;
this.paymentId = paymentId;
this.confirmations = confirmations;
this.transfers = transfers;
}
Random rnd = new Random();
public String toString() {
return getDirection() + "@" + getBlockHeight() + " " + getAmount();
return direction + "@" + blockheight + " " + amount;
}
public Direction getDirection() {
return TransactionInfo.Direction.values()[getDirectionJ()];
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(direction.getValue());
out.writeByte((byte) (isPending ? 1 : 0));
out.writeByte((byte) (isFailed ? 1 : 0));
out.writeLong(amount);
out.writeLong(fee);
out.writeLong(blockheight);
out.writeString(hash);
out.writeLong(timestamp);
out.writeString(paymentId);
out.writeLong(confirmations);
out.writeList(transfers);
out.writeString(txKey);
out.writeString(notes);
}
public native int getDirectionJ();
public native boolean isPending();
public native boolean isFailed();
public native long getAmount();
public native long getFee();
public native long getBlockHeight();
public native long getConfirmations();
public native String getHash();
public native long getTimestamp();
public native String getPaymentId();
/*
private List<Transfer> transfers;
public List<Transfer> getTransfers() { // not threadsafe
if (this.transfers == null) {
this.transfers = getTransfersJ();
public static final Parcelable.Creator<TransactionInfo> CREATOR = new Parcelable.Creator<TransactionInfo>() {
public TransactionInfo createFromParcel(Parcel in) {
return new TransactionInfo(in);
}
return this.transfers;
public TransactionInfo[] newArray(int size) {
return new TransactionInfo[size];
}
};
private TransactionInfo(Parcel in) {
direction = Direction.fromInteger(in.readInt());
isPending = in.readByte() != 0;
isFailed = in.readByte() != 0;
amount = in.readLong();
fee = in.readLong();
blockheight = in.readLong();
hash = in.readString();
timestamp = in.readLong();
paymentId = in.readString();
confirmations = in.readLong();
transfers = in.readArrayList(Transfer.class.getClassLoader());
txKey = in.readString();
notes = in.readString();
}
private native List<Transfer> getTransfersJ();
*/
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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.model;
import android.os.Parcel;
import android.os.Parcelable;
import com.m2049r.xmrwallet.util.TxData;
public class Transfer implements Parcelable {
public long amount;
public String address;
public Transfer(long amount, String address) {
this.amount = amount;
this.address = address;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(amount);
out.writeString(address);
}
public static final Parcelable.Creator<Transfer> CREATOR = new Parcelable.Creator<Transfer>() {
public Transfer createFromParcel(Parcel in) {
return new Transfer(in);
}
public Transfer[] newArray(int size) {
return new Transfer[size];
}
};
private Transfer(Parcel in) {
amount = in.readLong();
address = in.readString();
}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -28,8 +28,7 @@ public class Wallet {
static final String TAG = "Wallet";
public String getName() {
String p = getPath();
return new File(p).getName();
return new File(getPath()).getName();
}
private long handle = 0;
@@ -83,12 +82,13 @@ public class Wallet {
public native String getSecretViewKey();
public boolean store() {
return store(this.getPath());
return store("");
}
public native boolean store(String path);
public boolean close() {
disposePendingTransaction();
return WalletManager.getInstance().close(this);
}
@@ -162,15 +162,48 @@ public class Wallet {
//TODO virtual int autoRefreshInterval() const = 0;
//virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
// optional<uint64_t> tvAmount, uint32_t mixin_count,
// PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0;
private PendingTransaction pendingTransaction = null;
//virtual PendingTransaction * createSweepUnmixableTransaction() = 0;
public PendingTransaction getPendingTransaction() {
return pendingTransaction;
}
public void disposePendingTransaction() {
if (pendingTransaction != null) {
disposeTransaction(pendingTransaction);
pendingTransaction = null;
}
}
public PendingTransaction createTransaction(String dst_addr, String payment_id,
long amount, int mixin_count,
PendingTransaction.Priority priority) {
disposePendingTransaction();
int _priority = priority.getValue();
long txHandle = createTransactionJ(dst_addr, payment_id, amount, mixin_count, _priority);
pendingTransaction = new PendingTransaction(txHandle);
return pendingTransaction;
}
private native long createTransactionJ(String dst_addr, String payment_id,
long amount, int mixin_count,
int priority);
public PendingTransaction createSweepUnmixableTransaction() {
disposePendingTransaction();
long txHandle = createSweepUnmixableTransactionJ();
pendingTransaction = new PendingTransaction(txHandle);
return pendingTransaction;
}
private native long createSweepUnmixableTransactionJ();
//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0;
//virtual bool submitTransaction(const std::string &fileName) = 0;
//virtual void disposeTransaction(PendingTransaction * t) = 0;
public native void disposeTransaction(PendingTransaction pendingTransaction);
//virtual bool exportKeyImages(const std::string &filename) = 0;
//virtual bool importKeyImages(const std::string &filename) = 0;
@@ -201,9 +234,10 @@ public class Wallet {
public native void setDefaultMixin(int mixin);
//virtual bool setUserNote(const std::string &txid, const std::string &note) = 0;
//virtual std::string getUserNote(const std::string &txid) const = 0;
//virtual std::string getTxKey(const std::string &txid) const = 0;
public native boolean setUserNote(String txid, String note);
public native String getUserNote(String txid);
public native String getTxKey(String txid);
//virtual std::string signMessage(const std::string &message) = 0;
//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;

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)
private static WalletManager Instance = null;
public static WalletManager getInstance() { // TODO not threadsafe
public static synchronized WalletManager getInstance() {
if (WalletManager.Instance == null) {
WalletManager.Instance = new WalletManager();
}
@@ -57,7 +57,7 @@ public class WalletManager {
private void manageWallet(String walletId, Wallet wallet) {
if (getWallet(walletId) != null) {
throw new IllegalStateException("Wallet already under management!");
throw new IllegalStateException(walletId + " already under management!");
}
Log.d(TAG, "Managing " + walletId);
managedWallets.put(walletId, wallet);
@@ -65,7 +65,7 @@ public class WalletManager {
private void unmanageWallet(String walletId) {
if (getWallet(walletId) == null) {
throw new IllegalStateException("Wallet not under management!");
throw new IllegalStateException(walletId + " not under management!");
}
Log.d(TAG, "Unmanaging " + walletId);
managedWallets.remove(walletId);
@@ -78,6 +78,8 @@ public class WalletManager {
return wallet;
}
private native long createWalletJ(String path, String password, String language, boolean isTestNet);
public Wallet openWallet(String path, String password) {
long walletHandle = openWalletJ(path, password, isTestNet());
Wallet wallet = new Wallet(walletHandle);
@@ -85,6 +87,8 @@ public class WalletManager {
return wallet;
}
private native long openWalletJ(String path, String password, boolean isTestNet);
public Wallet recoveryWallet(String path, String mnemonic) {
Wallet wallet = recoveryWallet(path, mnemonic, 0);
manageWallet(wallet.getName(), wallet);
@@ -98,12 +102,17 @@ public class WalletManager {
return wallet;
}
private native long createWalletJ(String path, String password, String language, boolean isTestNet);
private native long openWalletJ(String path, String password, boolean isTestNet);
private native long recoveryWalletJ(String path, String mnemonic, boolean isTestNet, long restoreHeight);
public Wallet createWalletFromKeys(String path, String language, long restoreHeight,
String addressString, String viewKeyString, String spendKeyString) {
long walletHandle = createWalletFromKeysJ(path, language, isTestNet(), restoreHeight,
addressString, viewKeyString, spendKeyString);
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet.getName(), wallet);
return wallet;
}
private native long createWalletFromKeysJ(String path, String language,
boolean isTestNet,
long restoreHeight,

View File

@@ -26,7 +26,7 @@ import android.os.Process;
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
* The started Thread has a stck size of STACK_SIZE (=3MB)
* The started Thread has a stck size of STACK_SIZE (=5MB)
*/
public class MoneroHandlerThread extends Thread {
// from src/cryptonote_config.h

View File

@@ -18,10 +18,13 @@ package com.m2049r.xmrwallet.util;
import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import com.m2049r.xmrwallet.R;
@@ -43,7 +46,7 @@ public class Helper {
dir.mkdirs(); // try to make it
}
if (!dir.isDirectory()) {
String msg = "Directory " + dir.getAbsolutePath() + " does not exists.";
String msg = "Directory " + dir.getAbsolutePath() + " does not exist.";
Log.e(TAG, msg);
throw new IllegalStateException(msg);
}
@@ -56,7 +59,7 @@ public class Helper {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_DENIED) {
Log.d("permission", "permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
Log.d(TAG, "Permission denied to WRITE_EXTERNAL_STORAGE - requesting it");
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
context.requestPermissions(permissions, PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
return false;
@@ -82,4 +85,26 @@ public class Helper {
return Environment.MEDIA_MOUNTED.equals(state);
}
static public void showKeyboard(Activity act) {
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(act.getCurrentFocus(), InputMethodManager.SHOW_IMPLICIT);
}
static public void hideKeyboard(Activity act) {
if (act.getCurrentFocus() == null) {
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
} else {
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow((null == act.getCurrentFocus()) ? null : act.getCurrentFocus().getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
static public void showKeyboard(Dialog dialog) {
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
}
static public void hideKeyboardAlways(Activity act) {
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.util;
import android.os.Parcel;
import android.os.Parcelable;
import com.m2049r.xmrwallet.model.PendingTransaction;
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
public class TxData implements Parcelable {
public TxData(String dst_addr,
String paymentId,
long amount,
int mixin,
PendingTransaction.Priority priority) {
this.dst_addr = dst_addr;
this.paymentId = paymentId;
this.amount = amount;
this.mixin = mixin;
this.priority = priority;
}
public String dst_addr;
public String paymentId;
public long amount;
public int mixin;
public PendingTransaction.Priority priority;
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(dst_addr);
out.writeString(paymentId);
out.writeLong(amount);
out.writeInt(mixin);
out.writeInt(priority.getValue());
}
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
public static final Parcelable.Creator<TxData> CREATOR = new Parcelable.Creator<TxData>() {
public TxData createFromParcel(Parcel in) {
return new TxData(in);
}
public TxData[] newArray(int size) {
return new TxData[size];
}
};
private TxData(Parcel in) {
dst_addr = in.readString();
paymentId = in.readString();
amount = in.readLong();
mixin = in.readInt();
priority = PendingTransaction.Priority.fromInteger(in.readInt());
}
@Override
public int describeContents() {
return 0;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("dst_addr:");
sb.append(dst_addr);
sb.append(",paymentId:");
sb.append(paymentId);
sb.append(",amount:");
sb.append(amount);
sb.append(",mixin:");
sb.append(mixin);
sb.append(",priority:");
sb.append(priority.toString());
return sb.toString();
}
}

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<EditText
android:id="@+id/etWalletName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/generate_name_hint"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:textAlignment="center"
android:textSize="16sp" />
<EditText
android:id="@+id/etWalletPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="@string/generate_password_hint"
android:imeOptions="actionNext"
android:inputType="text"
android:textAlignment="center"
android:textSize="16sp" />
</LinearLayout>
<EditText
android:id="@+id/etWalletMnemonic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_mnemonic_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center"
android:textSize="16sp" />
<EditText
android:id="@+id/etWalletAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_address_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center"
android:textSize="16sp" />
<LinearLayout
android:id="@+id/llRestoreKeys"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<EditText
android:id="@+id/etWalletViewKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_viewkey_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center"
android:textSize="16sp" />
<EditText
android:id="@+id/etWalletSpendKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_spendkey_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center"
android:textSize="16sp" />
</LinearLayout>
<EditText
android:id="@+id/etWalletRestoreHeight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/generate_restoreheight_hint"
android:imeOptions="actionDone"
android:inputType="number"
android:textAlignment="center"
android:textSize="16sp"
android:visibility="gone" />
<Button
android:id="@+id/bGenerate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="@color/colorPrimary"
android:enabled="false"
android:text="@string/generate_buttonGenerate" />
</LinearLayout>

View File

@@ -0,0 +1,199 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<TextView
android:id="@+id/tvWalletLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/generate_wallet_label"
android:textAlignment="center"
android:textColor="@color/colorAccent"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletPasswordLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/generate_password_label"
android:textAlignment="center"
android:textColor="@color/colorAccent"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="horizontal"
android:weightSum="2">
<TextView
android:id="@+id/tvWalletName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="***"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textSize="16sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical">
<ProgressBar
android:id="@+id/pbProgress"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible" />
</LinearLayout>
<TextView
android:id="@+id/tvWalletMnemonicLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/generate_mnemonic_label"
android:textAlignment="center"
android:textColor="@color/colorAccent"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletMnemonic"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletAddressLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/generate_address_label"
android:textAlignment="center"
android:textColor="@color/colorAccent"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textIsSelectable="true"
android:selectAllOnFocus="true"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletViewKeyLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/generate_viewkey_label"
android:textAlignment="center"
android:textColor="@color/colorAccent"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletViewKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textIsSelectable="true"
android:selectAllOnFocus="true"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletSpendKeyLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/generate_spendkey_label"
android:textAlignment="center"
android:textColor="@color/colorAccent"
android:textSize="16sp" />
<TextView
android:id="@+id/tvWalletSpendKey"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textIsSelectable="true"
android:selectAllOnFocus="true"
android:textSize="16sp" />
<Button
android:id="@+id/bAccept"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/colorPrimary"
android:text="@string/generate_button_accept"
android:visibility="gone" />
<LinearLayout
android:id="@+id/llFunctions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:orientation="vertical"
android:visibility="gone">
<View
android:layout_width="match_parent"
android:layout_height="2dip"
android:background="@color/colorPrimary" />
<Button
android:id="@+id/bBackup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@color/colorPrimaryDark"
android:text="@string/generate_button_backup" />
<Button
android:id="@+id/bExport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:background="@color/colorPrimaryDark"
android:text="@string/generate_button_export" />
<Button
android:id="@+id/bDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="@color/colorAccent"
android:text="@string/generate_button_delete" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/tbLogin"
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"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@@ -1,18 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.m2049r.xmrwallet.LoginActivity">
android:orientation="vertical">
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_root"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="10dp" >

View File

@@ -0,0 +1,238 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<Spinner
android:id="@+id/sMixin"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:entries="@array/mixin"
android:textAlignment="center" />
<Spinner
android:id="@+id/sPriority"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:entries="@array/priority"
android:textAlignment="center" />
</LinearLayout>
<EditText
android:id="@+id/etAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/send_address_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="10">
<EditText
android:id="@+id/etPaymentId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="8"
android:hint="@string/send_paymentid_hint"
android:imeOptions="actionNext"
android:inputType="textMultiLine"
android:textAlignment="center"
android:textSize="16sp" />
<Button
android:id="@+id/bPaymentId"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:layout_weight="2"
android:background="@color/colorPrimary"
android:enabled="true"
android:text="@string/send_generate_paymentid_hint"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="10">
<EditText
android:id="@+id/etAmount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="8"
android:hint="@string/send_amount_hint"
android:imeOptions="actionDone"
android:inputType="numberDecimal"
android:textAlignment="center"
android:textSize="16sp" />
<Button
android:id="@+id/bSweep"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:layout_weight="2"
android:background="@color/colorPrimary"
android:enabled="true"
android:text="@string/send_sweep_hint"
android:visibility="invisible" />
</LinearLayout>
<Button
android:id="@+id/bPrepareSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="@color/colorPrimary"
android:enabled="false"
android:text="@string/send_prepare_hint" />
<ProgressBar
android:id="@+id/pbProgress"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<LinearLayout
android:id="@+id/llConfirmSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<Button
android:id="@+id/bDispose"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="@color/colorPrimary"
android:text="@string/send_dispose_hint" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="3">
<TextView
android:id="@+id/tvTxAmountLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/send_amount_label"
android:textAlignment="textEnd"
android:textColor="@color/colorAccent"
android:textSize="20sp" />
<TextView
android:id="@+id/tvTxAmount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="2"
android:textAlignment="textEnd"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="3">
<TextView
android:id="@+id/tvTxFeeLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/send_fee_label"
android:textAlignment="textEnd"
android:textColor="@color/colorAccent"
android:textSize="20sp" />
<TextView
android:id="@+id/tvTxFee"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="2"
android:textAlignment="textEnd"
android:textSize="20sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="3">
<TextView
android:id="@+id/tvTxDustLabel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:text="@string/send_dust_label"
android:textAlignment="textEnd"
android:textColor="@color/colorAccent"
android:textSize="20sp" />
<TextView
android:id="@+id/tvTxDust"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_weight="2"
android:textAlignment="textEnd"
android:textSize="20sp" />
</LinearLayout>
<EditText
android:id="@+id/etNotes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="@string/send_notes_hint"
android:imeOptions="actionDone"
android:inputType="textMultiLine"
android:textAlignment="center"
android:textSize="16sp" />
<Button
android:id="@+id/bSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="@color/colorPrimary"
android:text="@string/send_send_hint" />
</LinearLayout>
</LinearLayout>

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