Merge branch 'master' into move-to-mvvm

# Conflicts:
#	app/build.gradle
#	app/src/main/java/com/wallet/crypto/trustapp/controller/Controller.java
pull/2/head
MadCake 7 years ago
commit 7842327831
  1. 1
      .gitignore
  2. 3
      .idea/misc.xml
  3. 39
      README.md
  4. 4
      app/build.gradle
  5. 4
      app/src/androidTest/java/com/wallet/crypto/trustapp/AccountTest.java
  6. 44
      app/src/androidTest/java/com/wallet/crypto/trustapp/PasswordManagerTest.java
  7. 34
      app/src/androidTest/java/com/wallet/crypto/trustapp/ScreengrabTest.java
  8. 145
      app/src/androidTest/java/com/wallet/crypto/trustapp/views/CreateAccountTest.java
  9. 17
      app/src/main/AndroidManifest.xml
  10. 4
      app/src/main/cpp/native-lib.c
  11. 17
      app/src/main/java/com/wallet/crypto/trustapp/controller/CoinmarketService.java
  12. 199
      app/src/main/java/com/wallet/crypto/trustapp/controller/Controller.java
  13. 13
      app/src/main/java/com/wallet/crypto/trustapp/controller/EtherStore.java
  14. 17
      app/src/main/java/com/wallet/crypto/trustapp/controller/EthplorerService.java
  15. 49
      app/src/main/java/com/wallet/crypto/trustapp/controller/PasswordManager.java
  16. 34
      app/src/main/java/com/wallet/crypto/trustapp/model/CMTicker.java
  17. 98
      app/src/main/java/com/wallet/crypto/trustapp/model/EPAddressInfo.java
  18. 47
      app/src/main/java/com/wallet/crypto/trustapp/model/EPToken.java
  19. 54
      app/src/main/java/com/wallet/crypto/trustapp/model/EPTokenInfo.java
  20. 8
      app/src/main/java/com/wallet/crypto/trustapp/model/VMNetwork.java
  21. 2
      app/src/main/java/com/wallet/crypto/trustapp/views/AccountListActivity.java
  22. 2
      app/src/main/java/com/wallet/crypto/trustapp/views/CreateAccountActivity.java
  23. 2
      app/src/main/java/com/wallet/crypto/trustapp/views/ExportAccountActivity.java
  24. 2
      app/src/main/java/com/wallet/crypto/trustapp/views/ImportAccountActivity.java
  25. 29
      app/src/main/java/com/wallet/crypto/trustapp/views/RequestActivity.java
  26. 96
      app/src/main/java/com/wallet/crypto/trustapp/views/SendActivity.java
  27. 4
      app/src/main/java/com/wallet/crypto/trustapp/views/SettingsFragment.java
  28. 187
      app/src/main/java/com/wallet/crypto/trustapp/views/TokenListActivity.java
  29. 2
      app/src/main/java/com/wallet/crypto/trustapp/views/TransactionDetailFragment.java
  30. 21
      app/src/main/java/com/wallet/crypto/trustapp/views/TransactionListActivity.java
  31. 4
      app/src/main/java/com/wallet/crypto/trustapp/views/WarningBackupActivity.java
  32. BIN
      app/src/main/res/drawable-hdpi/token_icon.png
  33. BIN
      app/src/main/res/drawable-mdpi/token_icon.png
  34. BIN
      app/src/main/res/drawable-xhdpi/token_icon.png
  35. BIN
      app/src/main/res/drawable-xxhdpi/token_icon.png
  36. 3
      app/src/main/res/layout/account_list_content.xml
  37. 7
      app/src/main/res/layout/activity_create_account.xml
  38. 31
      app/src/main/res/layout/activity_request.xml
  39. 6
      app/src/main/res/layout/activity_send.xml
  40. 25
      app/src/main/res/layout/activity_token_list.xml
  41. 2
      app/src/main/res/layout/activity_transaction_list.xml
  42. 7
      app/src/main/res/layout/activity_warning_backup.xml
  43. 2
      app/src/main/res/layout/content_import_account.xml
  44. 24
      app/src/main/res/layout/content_token_list.xml
  45. 49
      app/src/main/res/layout/token_list_content.xml
  46. 3
      app/src/main/res/layout/transaction_list_content.xml
  47. 6
      app/src/main/res/menu/account_menu.xml
  48. 19
      app/src/main/res/menu/navigation.xml
  49. BIN
      app/src/main/res/mipmap-hdpi/token_logo.png
  50. BIN
      app/src/main/res/mipmap-mdpi/token_logo.png
  51. BIN
      app/src/main/res/mipmap-xhdpi/token_logo.png
  52. BIN
      app/src/main/res/mipmap-xxhdpi/token_logo.png
  53. BIN
      app/src/main/res/mipmap-xxxhdpi/token_logo.png
  54. 2
      app/src/main/res/values/strings.xml
  55. 21
      app/src/main/res/values/styles.xml
  56. BIN
      app/src/main/token_logo-web.png
  57. 15
      fastlane/Screengrabfile

1
.gitignore vendored

@ -24,6 +24,7 @@ gen/
# Gradle files
.gradle/
/build
/fastlane/metadata
# Local configuration file (sdk path, etc)
/local.properties

@ -24,4 +24,7 @@
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
</project>

@ -1 +1,38 @@
# Trust Wallet for Android
# Trust - Ethereum Wallet for Android
[![Build Status](https://travis-ci.org/TrustWallet/trust-wallet-android.svg?branch=master)](https://travis-ci.org/TrustWallet/trust-wallet-android)
[![License](https://img.shields.io/badge/license-GPL3-green.svg?style=flat)](https://github.com/fastlane/fastlane/blob/master/LICENSE)
Welcome to Trust's open source Android app!
## Getting Started
1. Install latest Android Studio (>=3.0 RC 2).
2. Clone this repository.
3. Register with etherscan.io and populate the API keys in Controller.java
4. Build and run.
Try the [app](https://play.google.com/store/apps/details?id=com.wallet.crypto.trustapp) on Google Play Store.
## Contributing
We intend for this project to be an educational resource: we are excited to
share our wins, mistakes, and methodology of android development as we work
in the open. Our primary focus is to continue improving the app for our users in
line with our roadmap.
The best way to submit feedback and report bugs is to open a GitHub issue.
Please be sure to include your operating system, device, version number, and
steps to reproduce reported bugs. Keep in mind that all participants will be
expected to follow our code of conduct.
## Code of Conduct
We aim to share our knowledge and findings as we work daily to improve our
product, for our community, in a safe and open space. We work as we live, as
kind and considerate human beings who learn and grow from giving and receiving
positive, constructive feedback. We reserve the right to delete or ban any
behavior violating this base foundation of respect.
Help with localization?
Here is a public link to join localization project: https://lokalise.co/signup/3947163159df13df851b51.98101647/all/

@ -8,8 +8,8 @@ android {
applicationId "com.wallet.crypto.trustapp"
minSdkVersion 16
targetSdkVersion 27
versionCode 4
versionName "1.3"
versionCode 10
versionName "1.3.7"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
multiDexEnabled = true

@ -25,7 +25,7 @@ public class AccountTest {
@Test
public void deleteAccountTest() {
Controller controller = Controller.get();
Controller controller = Controller.with(InstrumentationRegistry.getTargetContext());
VMAccount account = controller.createAccount("test password");
assert(account != null);
@ -40,7 +40,7 @@ public class AccountTest {
@Test
public void createAccountTest() {
Controller controller = Controller.get();
Controller controller = Controller.with(InstrumentationRegistry.getTargetContext());
VMAccount account = controller.createAccount("test password");
assert(account != null);
}

@ -0,0 +1,44 @@
package com.wallet.crypto.trustapp;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import com.wallet.crypto.trustapp.controller.PasswordManager;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
/**
* Created by marat on 11/14/17.
*/
public class PasswordManagerTest {
@Test
public void setGetPassword() throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, InvalidKeySpecException {
Context context = InstrumentationRegistry.getTargetContext();
PasswordManager.setPassword("myaddress", "mypassword", context);
assertThat(PasswordManager.getPassword("myaddress", context), is("mypassword"));
}
@Test
public void setGetPasswordLegacy() throws NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException, InvalidKeySpecException {
Context context = InstrumentationRegistry.getTargetContext();
PasswordManager.setPasswordLegacy("myaddress", "mypassword", context);
assertThat(PasswordManager.getPassword("myaddress", context), is("mypassword"));
}
}

@ -1,34 +0,0 @@
package com.wallet.crypto.trustapp;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import com.wallet.crypto.trustapp.views.TransactionListActivity;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
//import tools.fastlane.screengrab.Screengrab;
/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ScreengrabTest {
@Rule
public ActivityTestRule<TransactionListActivity> activityRule = new ActivityTestRule<>(TransactionListActivity.class);
@Test
public void takeTestScreenshot() throws Exception {
//Screengrab.screenshot("test_screenshot");
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
}
}

@ -0,0 +1,145 @@
package com.wallet.crypto.trustapp.views;
import android.support.test.espresso.ViewInteraction;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import com.wallet.crypto.trustapp.R;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import tools.fastlane.screengrab.Screengrab;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.scrollTo;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
@LargeTest
@RunWith(AndroidJUnit4.class)
public class CreateAccountTest {
@Rule
public ActivityTestRule<SplashActivity> mActivityTestRule = new ActivityTestRule<>(SplashActivity.class);
@Test
public void createAccountTest() {
Screengrab.screenshot("intro1");
ViewInteraction appCompatImageButton = onView(
allOf(withId(R.id.next),
childAtPosition(
allOf(withId(R.id.bottomContainer),
childAtPosition(
withId(R.id.bottom),
1)),
3),
isDisplayed()));
appCompatImageButton.perform(click());
Screengrab.screenshot("intro2");
ViewInteraction appCompatImageButton2 = onView(
allOf(withId(R.id.next),
childAtPosition(
allOf(withId(R.id.bottomContainer),
childAtPosition(
withId(R.id.bottom),
1)),
3),
isDisplayed()));
appCompatImageButton2.perform(click());
Screengrab.screenshot("intro3");
ViewInteraction appCompatButton = onView(
allOf(withId(R.id.done), withText("DONE"),
childAtPosition(
allOf(withId(R.id.bottomContainer),
childAtPosition(
withId(R.id.bottom),
1)),
4),
isDisplayed()));
appCompatButton.perform(click());
ViewInteraction appCompatButton2 = onView(
allOf(withId(R.id.create_account_button), withText("Create"),
childAtPosition(
childAtPosition(
withId(android.R.id.content),
0),
0),
isDisplayed()));
appCompatButton2.perform(click());
ViewInteraction appCompatButton3 = onView(
allOf(withId(R.id.later_button), withText("Do it later"),
childAtPosition(
childAtPosition(
withId(android.R.id.content),
0),
0),
isDisplayed()));
appCompatButton3.perform(click());
ViewInteraction appCompatButton4 = onView(
allOf(withId(android.R.id.button1), withText("OK"),
childAtPosition(
childAtPosition(
withId(R.id.buttonPanel),
0),
3)));
appCompatButton4.perform(scrollTo(), click());
// Added a sleep statement to match the app's execution delay.
// The recommended way to handle such scenarios is to use Espresso idling resources:
// https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/index.html
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ViewInteraction bottomNavigationItemView = onView(
allOf(withId(R.id.navigation_send),
childAtPosition(
childAtPosition(
withId(R.id.navigation),
0),
0),
isDisplayed()));
bottomNavigationItemView.perform(click());
}
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position " + position + " in parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup && parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent).getChildAt(position));
}
};
}
}

@ -3,16 +3,27 @@
package="com.wallet.crypto.trustapp">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.CAMERA" />
<!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Allows for storing and retrieving screenshots -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- Allows changing locales -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<!-- To auto-complete the email text field in the login form with the user's emails -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<application
android:name="android.support.multidex.MultiDexApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
@ -80,6 +91,10 @@
<activity
android:name=".views.WarningBackupActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".views.TokenListActivity"
android:label="@string/title_activity_token_list"
android:theme="@style/AppTheme.NoActionBar"></activity>
</application>
</manifest>

@ -8,7 +8,7 @@ JNIEXPORT jstring JNICALL
Java_com_wallet_crypto_trustapp_controller_PasswordManager_getKeyStringFromNative( JNIEnv* env, jobject thiz )
{
// TODO: fill in your key - must be 32 bytes
const jstring key = "35TheTru5tWa11ets3cr3tK3y377123!";
const jstring key = "ThisIsNotTheKeyYoureLookingFor!!";
return (*env)->NewStringUTF(env, key);
}
@ -16,6 +16,6 @@ JNIEXPORT jbyteArray JNICALL
Java_com_wallet_crypto_trustapp_controller_PasswordManager_getIvStringFromNative( JNIEnv* env, jobject thiz )
{
// TODO: fill in your iv - must be 16 bytes
const jstring iv = "8201va0184a0md8i";
const jstring iv = "NorTheInitVector";
return (*env)->NewStringUTF(env, iv);
}

@ -0,0 +1,17 @@
package com.wallet.crypto.trustapp.controller;
import com.wallet.crypto.trustapp.model.CMTicker;
import java.util.List;
import retrofit2.Call;
import retrofit2.http.GET;
/**
* Created by marat on 11/13/17.
*/
public interface CoinmarketService {
@GET("/v1/ticker/ethereum")
Call<List<CMTicker>> getEthereumPrice();
}

@ -18,6 +18,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.wallet.crypto.trustapp.R;
import com.wallet.crypto.trustapp.model.CMTicker;
import com.wallet.crypto.trustapp.model.ESTransaction;
import com.wallet.crypto.trustapp.model.ESTransactionListResponse;
import com.wallet.crypto.trustapp.model.VMAccount;
@ -29,13 +30,26 @@ import com.wallet.crypto.trustapp.views.ImportAccountActivity;
import com.wallet.crypto.trustapp.views.RequestActivity;
import com.wallet.crypto.trustapp.views.SendActivity;
import com.wallet.crypto.trustapp.views.SettingsActivity;
import com.wallet.crypto.trustapp.views.TokenListActivity;
import com.wallet.crypto.trustapp.views.TransactionListActivity;
import com.wallet.crypto.trustapp.views.WarningBackupActivity;
import org.ethereum.geth.Account;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Bool;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.abi.datatypes.generated.Int256;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.Web3jFactory;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCompileSolidity;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.EthGetTransactionCount;
import org.web3j.protocol.core.methods.response.EthSendTransaction;
@ -47,11 +61,13 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import retrofit2.Call;
@ -70,7 +86,6 @@ public class Controller {
public static final String KEY_ADDRESS = "key_address";
public static final String KEY_PASSWORD = "key_password";
private static Controller mInstance;
private static boolean mInited = false;
public static final int IMPORT_ACCOUNT_REQUEST = 1;
public static final int SHARE_RESULT = 2;
@ -81,6 +96,7 @@ public class Controller {
// Services
private EtherStore mEtherStore;
private CoinmarketService mCoinmarketService;
private SharedPreferences mPreferences;
// State
@ -90,48 +106,57 @@ public class Controller {
// View models
private ArrayList<VMNetwork> mNetworks;
private ArrayList<String> mEtherscanKeys;
private ArrayList<VMAccount> mAccounts;
private Map<String, List<ESTransaction>> mTransactions;
private CMTicker mEthTicker = null; // if null, no data available
// Views
private TransactionListActivity mTransactionListActivity;
// Backgroud task
private int mInterval = 10000;
// Background task
private int mInterval = 10000; // ms
private Handler mHandler;
public static Controller get() {
public static Controller with(Context context) {
if (mInstance == null) {
mInstance = new Controller();
synchronized (Controller.class) {
if (mInstance == null) {
mInstance = new Controller(context.getApplicationContext());
}
}
}
return mInstance;
}
protected Controller() { }
public void init(Context appContext, TransactionListActivity activity) {
protected Controller(Context context) {
mAppContext = context;
init(context);
}
if (mInited) {
return;
}
mInited = true;
public void init(Context appContext) {
mAppContext = appContext;
mTransactionListActivity = activity;
mKeystoreBaseDir = mAppContext.getFilesDir() + "/keystore";
mPreferences = mAppContext.getSharedPreferences("MyPref", 0); // 0 - for private mode
mEtherStore = new EtherStore(mKeystoreBaseDir);
mEtherStore = new EtherStore(mKeystoreBaseDir, this);
// Create etherscan key list
mEtherscanKeys = new ArrayList<>();
mEtherscanKeys.add("{etherscan_key1}");
mEtherscanKeys.add("{etherscan_key2}");
mEtherscanKeys.add("{etherscan_key3}");
// Create networks list
mNetworks = new ArrayList<>();
mNetworks.add(new VMNetwork("mainnet", "https://mainnet.infura.io/llyrtzQ3YhkdESt2Fzrk", "https://api.etherscan.io", "ZVU87DFQYV2TPJQKRJDITS42MW58GUEZ4V", 1));
mNetworks.add(new VMNetwork("kovan", "https://kovan.infura.io/llyrtzQ3YhkdESt2Fzrk", "https://kovan.etherscan.io", "ZVU87DFQYV2TPJQKRJDITS42MW58GUEZ4V", 42));
mNetworks.add(new VMNetwork("ropstein", "https://ropstein.infura.io/llyrtzQ3YhkdESt2Fzrk", "https://ropstein.etherscan.io", "ZVU87DFQYV2TPJQKRJDITS42MW58GUEZ4V", 3));
mNetworks.add(new VMNetwork("rinkeby", "https://rinkeby.infura.io/llyrtzQ3YhkdESt2Fzrk", "https://rinkeby.etherscan.io", "ZVU87DFQYV2TPJQKRJDITS42MW58GUEZ4V", 4));
mNetworks.add(new VMNetwork("mainnet", "https://mainnet.infura.io/llyrtzQ3YhkdESt2Fzrk", "https://api.etherscan.io", 1));
mNetworks.add(new VMNetwork("kovan", "https://kovan.infura.io/llyrtzQ3YhkdESt2Fzrk", "https://kovan.etherscan.io", 42));
mNetworks.add(new VMNetwork("ropstein", "https://ropstein.infura.io/llyrtzQ3YhkdESt2Fzrk", "https://ropstein.etherscan.io", 3));
mNetworks.add(new VMNetwork("rinkeby", "https://rinkeby.infura.io/llyrtzQ3YhkdESt2Fzrk", "https://rinkeby.etherscan.io", 4));
// Load current from app preferences
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(mAppContext);
@ -176,7 +201,6 @@ public class Controller {
}
public void setTransactionListActivity(TransactionListActivity activity) {
assert(mTransactionListActivity == null); // this should only be done once
mTransactionListActivity = activity;
}
@ -185,10 +209,14 @@ public class Controller {
public void run() {
try {
Log.d(TAG, "Periodic task");
mTransactionListActivity.fetchModelsAndReinit();
mTransactionListActivity.fetchModelsAndReinit();
} catch (Exception e) {
Log.e(TAG, "Unable to fetch update mTransactionListActivity");
} finally {
mHandler.postDelayed(mStatusChecker, mInterval);
}
fetchEthereumTicker();
}
};
@ -246,6 +274,12 @@ public class Controller {
return txns;
}
public void navigateToTokenList(Context context) {
Intent intent = new Intent(context, TokenListActivity.class);
intent.putExtra(KEY_ADDRESS, getCurrentAccount().getAddress());
context.startActivity(intent);
}
public void navigateToSettings(Context context) {
Intent intent = new Intent(context, SettingsActivity.class);
context.startActivity(intent);
@ -320,12 +354,33 @@ public class Controller {
new ImportAccountTask(keystore, password, listener).execute();
}
public void clickSend(SendActivity sendActivity, String from, String to, String ethAmount, OnTaskCompleted listener) {
public void clickSend(String from, String to, String ethAmount, OnTaskCompleted listener) {
Log.d(TAG, String.format("Send ETH: %s, %s, %s", from, to, ethAmount));
try {
String wei = EthToWei(ethAmount);
String password = PasswordManager.getPassword(from, mAppContext);
new SendTransactionTask(from, to, wei, password, listener).execute();
new SendTransactionTask(from, to, wei, password, null, listener).execute();
} catch (Exception e) {
Log.e(TAG, "Error sending transaction: ", e);
}
}
public void clickSendTokens(String from, String to, String contractAddress, String tokenAmount, int decimals, OnTaskCompleted listener) {
Log.d(TAG, String.format("Send tokens: %s, %s, %s", from, to, tokenAmount));
try {
BigInteger nTokens = new BigDecimal(tokenAmount).multiply(BigDecimal.valueOf((long)Math.pow(10, decimals))).toBigInteger();
List<Type> params = Arrays.<Type>asList(new Address(to), new Uint256(nTokens));
List<TypeReference<?>> returnTypes = Arrays.<TypeReference<?>>asList(new TypeReference<Bool>() { });
Function function = new Function("transfer", params, returnTypes);
String encodedFunction = FunctionEncoder.encode(function);
byte[] data = Numeric.hexStringToByteArray(Numeric.cleanHexPrefix(encodedFunction));
String password = PasswordManager.getPassword(from, mAppContext);
new SendTransactionTask(from, contractAddress, "0", password, data, listener).execute();
} catch (Exception e) {
Log.e(TAG, "Error sending transaction: ", e);
}
@ -386,7 +441,9 @@ public class Controller {
}
assert(mCurrentNetwork != null);
if (previous != mCurrentNetwork) {
mTransactionListActivity.fetchModelsAndReinit();
if (mTransactionListActivity != null) { // may not be set yet
mTransactionListActivity.fetchModelsAndReinit();
}
}
}
@ -474,6 +531,29 @@ public class Controller {
return version;
}
private class SendTokensTask extends AsyncTask<Void, Void, Void> {
String encodedFunction;
public SendTokensTask(String encodedFunction) {
this.encodedFunction = encodedFunction;
}
protected Void doInBackground(Void... params) {
Web3j web3 = Web3jFactory.build(new InfuraHttpService(mCurrentNetwork.getInfuraUrl()));
/*
Transaction transaction = Transaction.createFunctionCallTransaction(
from, gasPrice, gasLimit, contractAddress, amount, encodedFunction);
org.web3j.protocol.core.methods.response.EthSendTransaction transactionResponse =
web3.ethSendTransaction(transaction).sendAsync().get();
String transactionHash = transactionResponse.getTransactionHash();
String password = PasswordManager.getPassword(from, mAppContext);
*/
return null;
}
}
private class GetWeb3ClientVersionTask extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
try {
@ -566,18 +646,28 @@ public class Controller {
}
}
// Randomize etherscan key selection stay below rate limit
private String getRandomEtherscanKey() {
assert(mEtherscanKeys.size() > 0);
final int random = new Random().nextInt(mEtherscanKeys.size());
return mEtherscanKeys.get(random);
}
private class SendTransactionTask extends AsyncTask<Void, Void, Void> {
private String fromAddress;
private String toAddress;
private String wei;
private String password;
private byte[] data;
private OnTaskCompleted listener;
public SendTransactionTask(String fromAddress, String toAddress, String wei, String password, OnTaskCompleted listener) {
public SendTransactionTask(String fromAddress, String toAddress, String wei, String password, byte[] data, OnTaskCompleted listener) {
this.fromAddress = fromAddress;
this.toAddress = toAddress;
this.wei = wei;
this.password = password;
this.data = data;
this.listener = listener;
Log.d(TAG, "SendTransaction %s %s %s".format(fromAddress, toAddress, wei));
}
@ -599,7 +689,7 @@ public class Controller {
String hexValue = "0xDEADBEEF";
try {
byte[] signedMessage = mEtherStore.signTransaction(fromAccount, password, toAddress, wei, nonce.longValue());
byte[] signedMessage = mEtherStore.signTransaction(fromAccount, password, toAddress, wei, data, nonce.longValue());
hexValue = Numeric.toHexString(signedMessage);
} catch (Exception e) {
Log.e(TAG, "Error signing " + e.toString());
@ -636,6 +726,46 @@ public class Controller {
}
}
private void fetchEthereumTicker() {
try {
Retrofit mRetrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.coinmarketcap.com")
.build();
CoinmarketService service = mRetrofit.create(CoinmarketService.class);
Call<List<CMTicker>> call =
service.getEthereumPrice();
Log.d("INFO", "Request query:" + call.request().url().query());
call.enqueue(new Callback<List<CMTicker>>() {
@Override
public void onResponse(Call<List<CMTicker>> call, Response<List<CMTicker>> response) {
try {
List<CMTicker> tickers = response.body();
Log.d("INFO", "Number of transactions: " + tickers.size());
if (tickers.size() == 1) {
mEthTicker = tickers.get(0);
}
} catch (Exception e) {
Log.e(TAG, e.getLocalizedMessage());
}
}
@Override
public void onFailure(Call<List<CMTicker>> call, Throwable t) {
Log.e("ERROR", t.toString());
Toast.makeText(mAppContext, "Error contacting ether price service. Check internet connection.", Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
private class GetTransactionsTask extends AsyncTask<Void, Void, Void> {
private final List<VMAccount> mAccounts;
private final OnTaskCompleted mListener;
@ -656,7 +786,8 @@ public class Controller {
EtherscanService service = mRetrofit.create(EtherscanService.class);
Log.d(TAG, "Using etherscan service: " + mCurrentNetwork.getName() + ", " + mCurrentNetwork.getEtherscanUrl() + ", " + mCurrentNetwork.getEtherscanApiKey());
final String etherscanKey = getRandomEtherscanKey();
Log.d(TAG, "Using etherscan service: " + mCurrentNetwork.getName() + ", " + mCurrentNetwork.getEtherscanUrl() + ", " + etherscanKey);
Call<ESTransactionListResponse> call =
service.getTransactionList(
@ -665,7 +796,7 @@ public class Controller {
address,
"0",
"desc",
mCurrentNetwork.getEtherscanApiKey()
etherscanKey
);
@ -726,6 +857,20 @@ public class Controller {
return eth_scaled.toString();
}
public String EthToUsd(String balance) {
if (mEtherStore == null) {
return null;
}
try {
BigDecimal usd = new BigDecimal(balance).multiply(new BigDecimal(mEthTicker.getPriceUsd()));
usd = usd.setScale(2, RoundingMode.CEILING);
return usd.toString();
} catch (Exception e) {
Log.e(TAG, "Error converting ETH to USD");
}
return null;
}
public static String WeiToGwei(String wei) {
BigDecimal gwei = new BigDecimal(wei).divide(new BigDecimal(weiInGwei));
return gwei.toString();

@ -10,6 +10,7 @@ import org.ethereum.geth.BigInt;
import org.ethereum.geth.Geth;
import org.ethereum.geth.KeyStore;
import org.ethereum.geth.Transaction;
import org.web3j.protocol.core.methods.request.RawTransaction;
import java.nio.charset.Charset;
import java.util.ArrayList;
@ -24,8 +25,10 @@ public class EtherStore {
// private KeyChain keyChain;
private KeyStore ks;
private static String TAG = "EtherStore";
private Controller mController;
public EtherStore(String filesDir) {
public EtherStore(String filesDir, Controller controller) {
mController = controller;
ks = new KeyStore(filesDir + "/keystore", Geth.LightScryptN, Geth.LightScryptP);
Log.d(TAG, "Created KeyStore with %s accounts".format(Long.toString(ks.getAccounts().size())));
}
@ -59,20 +62,20 @@ public class EtherStore {
ks.deleteAccount(account, password);
}
public byte[] signTransaction(Account signer, String signerPassword, String toAddress, String wei, long nonce) throws Exception {
public byte[] signTransaction(Account signer, String signerPassword, String toAddress, String wei, byte[] data, long nonce) throws Exception {
BigInt value = new BigInt(Long.decode(wei));
BigInt gasPrice = new BigInt(0);
gasPrice.setString("15000000000", 10); // price, base
gasPrice.setString("1000000000", 10); // price, base
Transaction tx = new Transaction(
nonce, new Address(toAddress),
value,
new BigInt(90000), // gas limit
gasPrice,
null); // data
data); // data
BigInt chain = new BigInt(Controller.get().getCurrentNetwork().getChainId()); // Chain identifier of the main net
BigInt chain = new BigInt(mController.getCurrentNetwork().getChainId()); // Chain identifier of the main net
ks.unlock(signer, signerPassword);
Transaction signed = ks.signTx(signer, tx, chain);

@ -0,0 +1,17 @@
package com.wallet.crypto.trustapp.controller;
import com.wallet.crypto.trustapp.model.EPAddressInfo;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
import retrofit2.http.Path;
/**
* Created by marat on 11/16/17.
*/
public interface EthplorerService {
@GET("/getAddressInfo/{address}")
Call<EPAddressInfo> getAddressInfo(@Path("address") String address, @Query("apiKey") String apiKey);
}

@ -4,6 +4,7 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
@ -59,6 +60,12 @@ public final class PasswordManager {
System.loadLibrary("native-lib");
}
// These key and iv were compromised after being committed to the public github repo.
// To migrate old clients, we will first attempt to decrypt using these keys.
// TODO: remove once all 1.3.2 clients have upgraded
private final static String legacyKey = "35TheTru5tWa11ets3cr3tK3y377123!";
private final static String legacyIv = "8201va0184a0md8i";
public static native String getKeyStringFromNative();
public static native String getIvStringFromNative();
@ -69,6 +76,36 @@ public final class PasswordManager {
* ============================
*/
/**
* Encrypts the password and sets it into the shared preferences using legacy key and iv.
* @param password
* @param context
* @throws NoSuchPaddingException
* @throws BadPaddingException
* @throws NoSuchAlgorithmException
* @throws IllegalBlockSizeException
* @throws UnsupportedEncodingException
* @throws InvalidKeyException
* @throws InvalidKeySpecException
* @throws InvalidAlgorithmParameterException
*/
public static void setPasswordLegacy(final String address, final String password, final Context context)
throws NoSuchPaddingException, BadPaddingException, NoSuchAlgorithmException,
IllegalBlockSizeException, UnsupportedEncodingException, InvalidKeyException,
InvalidKeySpecException, InvalidAlgorithmParameterException
{
// encrypt password
SecretKey key = new SecretKeySpec(legacyKey.getBytes("UTF-8"), "AES");
IvParameterSpec iv = new IvParameterSpec(legacyIv.getBytes("UTF-8"));
final byte[] encryptedPassword = encrypt(password, key, iv);
// save in shared preferences
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(address + "-pwd", Base64.encodeToString(encryptedPassword, Base64.DEFAULT));
editor.commit();
}
/**
* Encrypts the password and sets it into the shared preferences.
* @param password
@ -121,6 +158,18 @@ public final class PasswordManager {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
final byte[] encryptedPassword = Base64.decode(sharedPreferences.getString(address + "-pwd", null), Base64.DEFAULT);
// Attempt to decrypt using legacy key/iv to migrate old clients
// TODO: remove once all clients have upgraded from 1.3.2
SecretKey oldKey = new SecretKeySpec(legacyKey.getBytes("UTF-8"), "AES");
IvParameterSpec oldIv = new IvParameterSpec(legacyIv.getBytes("UTF-8"));
try {
final String decryptedPassword = decrypt(encryptedPassword, oldKey, oldIv);
return decryptedPassword;
} catch (Exception e) {
Log.e("PASSMAN", e.getMessage());
}
// If decryption fails, it is most likely because this is a new client
// decrypt password
SecretKey key = new SecretKeySpec(getKeyStringFromNative().getBytes("UTF-8"), "AES");
IvParameterSpec iv = new IvParameterSpec(getIvStringFromNative().getBytes("UTF-8"));

@ -0,0 +1,34 @@
package com.wallet.crypto.trustapp.model;
import com.google.gson.annotations.SerializedName;
/**
* Created by marat on 11/13/17.
{
"id": "ethereum",
"name": "Ethereum",
"symbol": "ETH",
"rank": "2",
"price_usd": "314.862",
"price_btc": "0.0474923",
"24h_volume_usd": "1328580000.0",
"market_cap_usd": "30133198205.0",
"available_supply": "95702874.0",
"total_supply": "95702874.0",
"max_supply": null,
"percent_change_1h": "-0.1",
"percent_change_24h": "1.87",
"percent_change_7d": "4.21",
"last_updated": "1510589050"
}
*/
public class CMTicker {
@SerializedName("price_usd")
private String priceUsd;
public String getPriceUsd() {
return priceUsd;
}
}

@ -0,0 +1,98 @@
package com.wallet.crypto.trustapp.model;
import com.google.gson.annotations.SerializedName;
import java.util.List;
/**
* Created by marat on 11/16/17.
*/
/* This represents the following json
{
"address": "0x0122374ddd61ebdbe487f27225c8d55a96688714",
"ETH": {
"balance": 0.0081799448,
"totalIn": 23.9400569448,
"totalOut": 23.931877
},
"countTxs": 16,
"tokens": [{
"tokenInfo": {
"address": "0xab95e915c123fded5bdfb6325e35ef5515f1ea69",
"name": "XENON",
"decimals": 18,
"symbol": "XNN",
"totalSupply": "1000000000000000000000000000",
"owner": "0x",
"lastUpdated": 1510850050,
"issuancesCount": 0,
"holdersCount": 756611,
"description": "XenonNetwork (http://xenon.network/), an enterprise-scale blockchain launching in July 2018 begins a massive distribution of their native Xenon (XNN) ERC-20 compatible tokens to over 400,000 active ethereum addresses at the beginning of October. In addition to this, a similar distribution to bitcoin holders will occur in November, followed by a proof-of-individuality public token distribution from November through to June 2018.\n\nhttp://xenon.network",
"price": false
},
"balance": 6.9576614445292e+20,
"totalIn": 0,
"totalOut": 0
}, {
"tokenInfo": {
"address": "0x0cf0ee63788a0849fe5297f3407f701e122cc023",
"name": "DATAcoin",
"decimals": 18,
"symbol": "DATA",
"totalSupply": "987154514000000000000000000",
"owner": "0x1bb7804d12fa4f70ab63d0bbe8cb0b1992694338",
"lastUpdated": 1510851220,
"totalIn": 2.165e+26,
"totalOut": 2.165e+26,
"issuancesCount": 0,
"holdersCount": 433497,
"price": false
},
"balance": 1.0541017210196e+18,
"totalIn": 0,
"totalOut": 0
}, {
"tokenInfo": {
"address": "0xd0a4b8946cb52f0661273bfbc6fd0e0c75fc6433",
"name": "Storm Token",
"decimals": "18",
"symbol": "STORM",
"totalSupply": "8422653913958765221271563784",
"owner": "0x00250bf60e31c4ec7e8c04bcca4af8e294306e25",
"lastUpdated": 1510845529,
"issuancesCount": 0,
"holdersCount": 974,
"price": false
},
"balance": 2.156e+22,
"totalIn": 0,
"totalOut": 0
}, {
"tokenInfo": {
"address": "0x519475b31653e46d20cd09f9fdcf3b12bdacb4f5",
"name": "VIU",
"decimals": "18",
"symbol": "VIU",
"totalSupply": "1000000000000000000000000000",
"owner": "0x",
"lastUpdated": 1510851386,
"issuancesCount": 0,
"holdersCount": 441605,
"price": false
},
"balance": 1.984985648e+20,
"totalIn": 0,
"totalOut": 0
}]
}
*/
public class EPAddressInfo {
@SerializedName("tokens")
private List<EPToken> tokens;
public List<EPToken> getTokens() {
return tokens;
}
}

@ -0,0 +1,47 @@
package com.wallet.crypto.trustapp.model;
import com.google.gson.annotations.SerializedName;
/**
* Created by marat on 11/16/17.
*
*/
/* Represents the following JSON
{
"tokenInfo": {
"address": "0x0cf0ee63788a0849fe5297f3407f701e122cc023",
"name": "DATAcoin",
"decimals": 18,
"symbol": "DATA",
"totalSupply": "987154514000000000000000000",
"owner": "0x1bb7804d12fa4f70ab63d0bbe8cb0b1992694338",
"lastUpdated": 1510851220,
"totalIn": 2.165e+26,
"totalOut": 2.165e+26,
"issuancesCount": 0,
"holdersCount": 433497,
"price": false
},
"balance": 1.0541017210196e+18,
"totalIn": 0,
"totalOut": 0
}
*/
public class EPToken {
@SerializedName("tokenInfo")
private EPTokenInfo tokenInfo;
@SerializedName("balance")
private double balance;
public EPTokenInfo getTokenInfo() {
return tokenInfo;
}
public double getBalance() {
return balance;
}
}

@ -0,0 +1,54 @@
package com.wallet.crypto.trustapp.model;
import com.google.gson.annotations.SerializedName;
/**
* Created by marat on 11/16/17.
*/
/* Represents the following JSON
{
"address": "0x0cf0ee63788a0849fe5297f3407f701e122cc023",
"name": "DATAcoin",
"decimals": 18,
"symbol": "DATA",
"totalSupply": "987154514000000000000000000",
"owner": "0x1bb7804d12fa4f70ab63d0bbe8cb0b1992694338",
"lastUpdated": 1510851220,
"totalIn": 2.165e+26,
"totalOut": 2.165e+26,
"issuancesCount": 0,
"holdersCount": 433497,
"price": false
}
*/
public class EPTokenInfo {
@SerializedName("address")
private String address;
@SerializedName("name")
private String name;
@SerializedName("symbol")
private String symbol;
@SerializedName("decimals")
private int decimals;
public String getAddress() {
return address;
}
public String getName() {
return name;
}
public String getSymbol() {
return symbol;
}
public int getDecimals() {
return decimals;
}
}

@ -8,14 +8,12 @@ public class VMNetwork {
private String name;
private String infuraUrl;
private String etherscanUrl;
private String etherscanApiKey;
private int chainId;
public VMNetwork(String name, String infuraUrl, String etherscanUrl, String etherscanApiKey, int chainId) {
public VMNetwork(String name, String infuraUrl, String etherscanUrl, int chainId) {
this.name = name;
this.infuraUrl = infuraUrl;
this.etherscanUrl = etherscanUrl;
this.etherscanApiKey = etherscanApiKey;
this.chainId = chainId;
}
@ -32,10 +30,6 @@ public class VMNetwork {
return etherscanUrl;
}
public String getEtherscanApiKey() {
return etherscanApiKey;
}
public int getChainId() {
return chainId;
}

@ -56,7 +56,7 @@ public class AccountListActivity extends AppCompatActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mController = Controller.get();
mController = Controller.with(this);
mRecyclerView = findViewById(R.id.account_list);
assert mRecyclerView != null;

@ -34,7 +34,7 @@ public class CreateAccountActivity extends AppCompatActivity {
ActionBar actionBar = getSupportActionBar();
mController = Controller.get();
mController = Controller.with(this);
if (mController.getNumberOfAccounts() == 0) {
showIntro();

@ -45,7 +45,7 @@ public class ExportAccountActivity extends AppCompatActivity {
getSupportActionBar().setTitle(getString(R.string.title_backup) + ": " + mAddress.substring(0, 5) + "...");
mController = Controller.get();
mController = Controller.with(this);
mPasswordText = findViewById(R.id.export_password);
mConfirmPasswordText = findViewById(R.id.confirm_password);

@ -35,7 +35,7 @@ public class ImportAccountActivity extends AppCompatActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mController = Controller.get();
mController = Controller.with(this);
mKeystore = (EditText) findViewById(R.id.import_keystore);
mPassword = (EditText) findViewById(R.id.import_password);

@ -16,10 +16,12 @@ import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.journeyapps.barcodescanner.BarcodeEncoder;
import com.wallet.crypto.trustapp.R;
import com.wallet.crypto.trustapp.controller.Controller;
import com.wallet.crypto.trustapp.model.VMAccount;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
@ -48,7 +50,7 @@ public class RequestActivity extends AppCompatActivity {
addressTextView = (TextView)findViewById(R.id.addressTextView);
copyButton = findViewById(R.id.copy_button);
VMAccount account = Controller.get().getCurrentAccount();
VMAccount account = Controller.with(getApplicationContext()).getCurrentAccount();
addressTextView.setText(account.getAddress());
@ -63,7 +65,12 @@ public class RequestActivity extends AppCompatActivity {
}
});
new GenerateQRCodeTask(ETHEREUM_PREFIX + Controller.get().getCurrentAccount().getAddress() + "?value=0").execute();
try {
final Bitmap qrCode = TextToImageEncode(ETHEREUM_PREFIX + Controller.with(this).getCurrentAccount().getAddress() + "?value=0");
imageView.setImageBitmap(qrCode);
} catch(Exception e) {
Log.d(TAG, e.getMessage());
}
}
Bitmap TextToImageEncode(String Value) throws WriterException {
@ -79,24 +86,10 @@ public class RequestActivity extends AppCompatActivity {
return null;
}
int bitMatrixWidth = bitMatrix.getWidth();
int bitMatrixHeight = bitMatrix.getHeight();
int[] pixels = new int[bitMatrixWidth * bitMatrixHeight];
for (int y = 0; y < bitMatrixHeight; y++) {
int offset = y * bitMatrixWidth;
for (int x = 0; x < bitMatrixWidth; x++) {
pixels[offset + x] = bitMatrix.get(x, y) ?
getResources().getColor(R.color.QRCodeBlackColor):getResources().getColor(R.color.QRCodeWhiteColor);
}
}
Bitmap bitmap = Bitmap.createBitmap(bitMatrixWidth, bitMatrixHeight, Bitmap.Config.ARGB_4444);
BarcodeEncoder barcodeEncoder = new BarcodeEncoder();
Bitmap bitmap = barcodeEncoder.createBitmap(bitMatrix);
bitmap.setPixels(pixels, 0, QRcodeWidth, 0, 0, bitMatrixWidth, bitMatrixHeight);
return bitmap;
}

@ -38,8 +38,17 @@ public class SendActivity extends AppCompatActivity {
private static final String LOG_TAG = SendActivity.class.getSimpleName();
private static final int BARCODE_READER_REQUEST_CODE = 1;
public static final String EXTRA_SENDING_TOKENS = "extra_sending_tokens";
public static final String EXTRA_CONTRACT_ADDRESS = "extra_contract_address";
public static final String EXTRA_SYMBOL = "extra_symbol";
public static final String EXTRA_DECIMALS = "extra_decimals";
private TextView mResultTextView;
private boolean mSendingTokens = false;
private String mContractAddress;
private String mSymbol;
private int mDecimals;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -51,7 +60,7 @@ public class SendActivity extends AppCompatActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mController = Controller.get();
mController = Controller.with(this);
List<VMAccount> accounts = mController.getAccounts();
@ -63,6 +72,20 @@ public class SendActivity extends AppCompatActivity {
mTo.setText(toAddress);
}
mContractAddress = getIntent().getStringExtra(EXTRA_CONTRACT_ADDRESS);
mDecimals = getIntent().getIntExtra(EXTRA_DECIMALS, -1);
mSymbol = getIntent().getStringExtra(EXTRA_SYMBOL);
mSendingTokens = getIntent().getBooleanExtra(EXTRA_SENDING_TOKENS, false);
assert(!mSendingTokens || (mSendingTokens && mDecimals > -1 && mContractAddress != null));
EditText amountView = findViewById(R.id.amount);
if (mSendingTokens && mSymbol != null) {
amountView.setHint(mSymbol + " amount");
} else {
amountView.setHint("ETH amount");
}
Button mSendButton = (Button) findViewById(R.id.send_button);
mSendButton.setOnClickListener(new View.OnClickListener() {
@Override
@ -78,8 +101,8 @@ public class SendActivity extends AppCompatActivity {
}
final String amount = mAmount.getText().toString();
if (!isInt(amount)) {
mAmount.setError("Must be an integer");
if (!isValidEthAmount(amount)) {
mAmount.setError("Invalid amount");
inputValid = false;
}
@ -87,25 +110,47 @@ public class SendActivity extends AppCompatActivity {
return;
}
mController.clickSend(
SendActivity.this,
mController.getCurrentAccount().getAddress(),
mTo.getText().toString(),
mAmount.getText().toString(),
new OnTaskCompleted() {
public void onTaskCompleted(final TaskResult result) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (result.getStatus() == TaskStatus.SUCCESS) {
SendActivity.this.finish();
}
Toast.makeText(SendActivity.this, result.getMessage(), Toast.LENGTH_LONG).show();
if (mSendingTokens) {
mController.clickSendTokens(
mController.getCurrentAccount().getAddress(),
mTo.getText().toString(),
mContractAddress,
mAmount.getText().toString(),
mDecimals,
new OnTaskCompleted() {
public void onTaskCompleted(final TaskResult result) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (result.getStatus() == TaskStatus.SUCCESS) {
SendActivity.this.finish();
}
Toast.makeText(SendActivity.this, result.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
});
}
}
);
}
);
} else {
mController.clickSend(
mController.getCurrentAccount().getAddress(),
mTo.getText().toString(),
mAmount.getText().toString(),
new OnTaskCompleted() {
public void onTaskCompleted(final TaskResult result) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (result.getStatus() == TaskStatus.SUCCESS) {
SendActivity.this.finish();
}
Toast.makeText(SendActivity.this, result.getMessage(), Toast.LENGTH_LONG).show();
}
});
}
}
);
}
}
});
@ -130,10 +175,11 @@ public class SendActivity extends AppCompatActivity {
}
}
boolean isInt(String value) {
if (value.matches("\\d+")) {
return true;
} else {
boolean isValidEthAmount(String eth) {
try {
String wei = Controller.EthToWei(eth);
return wei != null;
} catch (Exception e) {
return false;
}
}

@ -27,7 +27,7 @@ public class SettingsFragment extends PreferenceFragment
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.fragment_settings);
mController = Controller.get();
mController = Controller.with(getActivity());
final Preference donate = findPreference("pref_donate");
@ -65,7 +65,7 @@ public class SettingsFragment extends PreferenceFragment
}
});
String versionString = Controller.get().getVersion();
String versionString = Controller.with(getActivity()).getVersion();
Preference version = findPreference("pref_version");
version.setSummary(versionString);

@ -0,0 +1,187 @@
package com.wallet.crypto.trustapp.views;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.wallet.crypto.trustapp.R;
import com.wallet.crypto.trustapp.controller.Controller;
import com.wallet.crypto.trustapp.controller.EthplorerService;
import com.wallet.crypto.trustapp.model.EPAddressInfo;
import com.wallet.crypto.trustapp.model.EPToken;
import com.wallet.crypto.trustapp.model.EPTokenInfo;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class TokenListActivity extends AppCompatActivity {
private static String TAG = "TOKENS";
private String mAddress;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_token_list);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
}
mAddress = getIntent().getStringExtra(Controller.KEY_ADDRESS);
RecyclerView recyclerView = findViewById(R.id.token_list);
setupRecyclerView(recyclerView);
}
private void setupRecyclerView(final @NonNull RecyclerView recyclerView) {
try {
Retrofit retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.ethplorer.io")
.build();
EthplorerService service = retrofit.create(EthplorerService.class);
Call<EPAddressInfo> call = service.getAddressInfo(mAddress, "freekey");
call.enqueue(new Callback<EPAddressInfo>() {
@Override
public void onResponse(Call<EPAddressInfo> call, Response<EPAddressInfo> response) {
try {
Log.d(TAG, Integer.toString(response.body().getTokens().size()));
EPAddressInfo addressInfo = response.body();
recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(addressInfo.getTokens()));
} catch (Exception e) {
Log.e(TAG, e.getLocalizedMessage());
}
}
@Override
public void onFailure(Call<EPAddressInfo> call, Throwable t) {
Log.e("ERROR", t.toString());
Toast.makeText(TokenListActivity.this.getApplicationContext(), "Error contacting token service. Check internet connection.", Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public class SimpleItemRecyclerViewAdapter
extends RecyclerView.Adapter<SimpleItemRecyclerViewAdapter.ViewHolder> {
private final List<EPToken> mValues;
public SimpleItemRecyclerViewAdapter(List<EPToken> items) {
mValues = items;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.token_list_content, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mItem = mValues.get(position);
EPToken token = holder.mItem;
final EPTokenInfo info = token.getTokenInfo();
try {
holder.mNameView.setText(info.getName());
holder.mSymbolView.setText(info.getSymbol());
BigDecimal balance = new BigDecimal(token.getBalance());
BigDecimal decimalDivisor = new BigDecimal(Math.pow(10, info.getDecimals()));
balance = info.getDecimals() > 0 ? balance.divide(decimalDivisor) : balance;
balance = balance.setScale(2, RoundingMode.HALF_UP);
holder.mBalanceView.setText(balance.toString());
holder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Context context = v.getContext();
Intent intent = new Intent(context, SendActivity.class);
intent.putExtra(SendActivity.EXTRA_SENDING_TOKENS, true);
intent.putExtra(SendActivity.EXTRA_CONTRACT_ADDRESS, info.getAddress());
intent.putExtra(SendActivity.EXTRA_SYMBOL, info.getSymbol());
intent.putExtra(SendActivity.EXTRA_DECIMALS, info.getDecimals());
context.startActivity(intent);
}
});
} catch (Exception e) {
holder.mNameView.setText("N/A");
holder.mSymbolView.setText("N/A");
holder.mBalanceView.setText("-");
}
}
@Override
public int getItemCount() {
return mValues.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final TextView mNameView;
public final TextView mSymbolView;
public final TextView mBalanceView;
public EPToken mItem;
public ViewHolder(View view) {
super(view);
mView = view;
mNameView = view.findViewById(R.id.name);
mSymbolView = view.findViewById(R.id.symbol);
mBalanceView = view.findViewById(R.id.balance);
}
@Override
public String toString() {
return super.toString();
}
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

@ -41,7 +41,7 @@ public class TransactionDetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Controller mController = Controller.get();
Controller mController = Controller.with(getActivity());
Log.d(TAG, getArguments().toString());

@ -51,6 +51,9 @@ public class TransactionListActivity extends AppCompatActivity {
case R.id.navigation_send:
mController.navigateToSend(TransactionListActivity.this);
break;
case R.id.navigation_tokens:
mController.navigateToTokenList(TransactionListActivity.this);
break;
}
return false;
}
@ -63,8 +66,8 @@ public class TransactionListActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transaction_list);
mController = Controller.get();
mController.init(getApplicationContext(), this);
mController = Controller.with(getApplicationContext());
mController.setTransactionListActivity(this);
BottomNavigationView navigation = findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
@ -115,7 +118,17 @@ public class TransactionListActivity extends AppCompatActivity {
String balance = Controller.WeiToEth(account.getBalance().toString(), 5);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setTitle(mAddress.substring(0, 5) + "... : " + balance + " ETH");
String usd = Controller.with(this).EthToUsd(balance);
// Conversion data may not be available, in which case, hide it
if (usd != null) {
getSupportActionBar().setTitle("$" + usd);
getSupportActionBar().setSubtitle(balance + " ETH");
} else {
getSupportActionBar().setTitle(balance + " ETH");
getSupportActionBar().setSubtitle(mAddress);
}
toolbar.inflateMenu(R.menu.transaction_list_menu);
} catch (Exception e) {
Log.e(TAG, "Error updating balance: ", e);
@ -198,7 +211,7 @@ public class TransactionListActivity extends AppCompatActivity {
}
private void setupRecyclerView(@NonNull RecyclerView recyclerView) {
Controller controller = Controller.get();
Controller controller = Controller.with(this);
List<ESTransaction> txns = controller.getTransactions(mAddress);
recyclerView.setAdapter(new SimpleItemRecyclerViewAdapter(txns));
}

@ -23,7 +23,7 @@ public class WarningBackupActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_warning_backup);
final Controller controller = Controller.get();
final Controller controller = Controller.with(this);
mAddress = this.getIntent().getStringExtra(Controller.KEY_ADDRESS);
mPassword = this.getIntent().getStringExtra(Controller.KEY_PASSWORD);
@ -66,7 +66,7 @@ public class WarningBackupActivity extends AppCompatActivity {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == Controller.SHARE_RESULT) {
if (resultCode == RESULT_OK) {
if (Controller.get().getAccounts().size() == 1) {
if (Controller.with(this).getAccounts().size() == 1) {
Intent intent = new Intent(getApplicationContext(), TransactionListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 986 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -2,6 +2,7 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:padding="8dp"
android:descendantFocusability="blocksDescendants">
@ -32,7 +33,7 @@
android:id="@+id/delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_delete_forever_black_24dp"
app:srcCompat="@drawable/ic_delete_forever_black_24dp"
android:background="@null"
android:layout_centerVertical="true"
android:layout_alignParentRight="true" />

@ -17,7 +17,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_sign_in"
android:textStyle="bold" />
/>
<TextView
android:layout_width="fill_parent"
@ -28,11 +28,10 @@
<Button
android:id="@+id/import_account_button"
style="?android:textAppearanceSmall"
style="@style/Widget.AppCompat.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_import"
android:textStyle="bold" />
android:text="@string/action_import" />
</LinearLayout>

@ -3,9 +3,9 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:padding="16dp"
tools:context="com.wallet.crypto.trustapp.views.RequestActivity"
>
tools:context="com.wallet.crypto.trustapp.views.RequestActivity">
<TextView
android:id="@+id/addressLabel"
@ -16,30 +16,31 @@
android:layout_centerHorizontal="true"
android:gravity="center"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="500px"
android:id="@+id/imageView"
app:srcCompat="@drawable/ic_autorenew_black_24dp"
android:layout_centerHorizontal="true"
android:layout_below="@+id/addressLabel"
/>
<TextView
android:id="@+id/addressTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/addressTextView"
android:layout_below="@+id/addressLabel"
android:layout_below="@+id/imageView"
android:textSize="12sp"
android:layout_centerHorizontal="true"
android:gravity="center"
/>
<Button
android:id="@+id/copy_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/copy"
android:id="@+id/copy_button"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_below="@+id/addressTextView"
android:layout_centerHorizontal="true" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="500dp"
android:id="@+id/imageView"
android:src="@drawable/ic_autorenew_black_24dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
/>
</RelativeLayout>

@ -43,7 +43,6 @@
android:layout_height="wrap_content"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionUnspecified"
android:hint="@string/prompt_amount"
android:inputType="numberDecimal"
android:maxLines="1" />
@ -51,12 +50,11 @@
<Button
android:id="@+id/send_button"
style="?android:textAppearanceSmall"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/action_send"
android:textStyle="bold" />
android:text="@string/action_send" />
<TextView
android:id="@+id/result_textview"

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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="match_parent"
tools:context="com.wallet.crypto.trustapp.views.TokenListActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_token_list" />
</android.support.design.widget.CoordinatorLayout>

@ -18,6 +18,8 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
android:title="Title"
android:subtitle="subtitle"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>

@ -15,7 +15,8 @@
android:layout_marginBottom="8dp"
android:text="Do it later"
app:layout_constraintBottom_toBottomOf="parent"
tools:layout_editor_absoluteX="0dp" />
style="@style/Widget.AppCompat.Button"
/>
<Button
android:id="@+id/backup_button"
@ -25,7 +26,7 @@
android:layout_marginBottom="8dp"
android:text="Backup Wallet"
app:layout_constraintBottom_toTopOf="@+id/later_button"
tools:layout_editor_absoluteX="0dp" />
/>
<TextView
android:id="@+id/textView"
@ -38,7 +39,7 @@
app:layout_constraintBottom_toTopOf="@+id/backup_button"
app:layout_constraintTop_toBottomOf="@+id/textView3"
app:layout_constraintVertical_bias="0.067"
tools:layout_editor_absoluteX="0dp" />
/>
<ImageView
android:id="@+id/imageView2"

@ -58,7 +58,7 @@
<Button
android:id="@+id/import_account_button"
style="?android:textAppearanceSmall"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.wallet.crypto.trustapp.views.TokenListActivity"
tools:showIn="@layout/activity_token_list">
<android.support.v7.widget.RecyclerView 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:id="@+id/token_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginLeft="0dp"
android:layout_marginRight="0dp"
app:layoutManager="LinearLayoutManager"
tools:context="com.wallet.crypto.trustapp.views.TokenListActivity"
tools:listitem="@layout/token_list_content" />
</android.support.constraint.ConstraintLayout>

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<ImageView
android:id="@+id/logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/token_logo"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1"
android:text="Trust"
android:textAppearance="?attr/textAppearanceListItem"
android:layout_toRightOf="@id/logo"
android:layout_alignParentTop="true" />
<TextView
android:id="@+id/symbol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/logo"
android:layout_below="@+id/name"
android:layout_weight="1"
android:maxLines="1"
android:text="TRST"
android:textAppearance="?attr/textAppearanceListItem"
android:textSize="11dp" />
<TextView
android:id="@+id/balance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="13dp"
android:layout_marginEnd="13dp"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
</RelativeLayout>

@ -11,7 +11,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="start"
android:maxLines="1"
android:text="Sent"
android:textAppearance="?attr/textAppearanceListItem"
@ -25,7 +24,6 @@
android:layout_height="wrap_content"
android:textSize="11dp"
android:layout_weight="1"
android:ellipsize="middle"
android:maxLines="1"
android:text="0xDEADBEEF"
android:textAppearance="?attr/textAppearanceListItem"
@ -39,7 +37,6 @@
android:layout_height="wrap_content"
android:textSize="11dp"
android:layout_weight="1"
android:ellipsize="middle"
android:maxLines="1"
android:text="0xDEADBEEF"
android:textAppearance="?attr/textAppearanceListItem"

@ -7,7 +7,11 @@
android:title="@string/title_send" />
<item
android:id="@+id/navigation_receive"
android:icon="@drawable/ic_add_circle_black_24dp"
android:icon="@mipmap/qr_code_icon"
android:title="@string/title_request" />
<item
android:id="@+id/navigation_tokens"
android:icon="@drawable/token_icon"
android:title="@string/title_tokens" />
</menu>

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home" />
<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard" />
<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications" />
</menu>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -100,5 +100,7 @@
<string name="title_privacy_policy">Privacy Policy</string>
<string name="title_terms">Terms and Conditions</string>
<string name="title_version">Version</string>
<string name="title_activity_token_list">Tokens</string>
<string name="title_tokens">My Tokens</string>
</resources>

@ -25,4 +25,25 @@
</style>
<style name="ToolbarTextAppearance">
<item name="android:textColor">@android:color/white</item>
<item name="android:shadowDx">1</item>
<item name="android:shadowDy">1</item>
<item name="android:shadowRadius">2</item>
<item name="android:shadowColor">?colorAccent</item>
</style>
<style name="ToolbarTextAppearance.Title">
<item name="android:textSize">20sp</item>
</style>
<style name="ToolbarTextAppearance.Subtitle">
<item name="android:textSize">14sp</item>
</style>
<style name="MyToolbar">
<item name="theme">@style/ThemeOverlay.AppCompat.Dark</item>
<item name="android:background">?colorPrimary</item>
</style>
</resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@ -0,0 +1,15 @@
# remove the leading '#' to uncomment lines
app_package_name 'com.wallet.crypto.trustapp'
# use_tests_in_packages ['your.screenshot.tests.package']
app_apk_path './app/build/outputs/apk/app-debug.apk'
tests_apk_path './app/build/outputs/apk/app-debug-androidTest.apk'
locales ['en-US']
# clear all previously generated screenshots in your local output directory before creating new ones
clear_previous_screenshots true
# For more information about all available options run
# fastlane screengrab --help
Loading…
Cancel
Save