#16 - added Dapp list remembering and implemented dapps dialogs.

Partially implemented json wallet import.
This commit is contained in:
Adrian Tiberius 2015-08-12 06:01:50 +02:00
parent f3bb5165d6
commit c74594f68d
9 changed files with 319 additions and 69 deletions

View File

@ -28,6 +28,7 @@ import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
@ -37,8 +38,15 @@ import com.afollestad.materialdialogs.MaterialDialog;
import com.bumptech.glide.Glide;
import org.ethereum.crypto.HashUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.io.File;
import java.io.FileInputStream;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -61,16 +69,14 @@ import static org.ethereum.config.SystemProperties.CONFIG;
public abstract class BaseActivity extends AppCompatActivity implements
OnClickListener, OnDAppClickListener, OnProfileClickListener {
private static final Logger logger = LoggerFactory.getLogger("SyngApplication");
private static final int DRAWER_CLOSE_DELAY_SHORT = 200;
private static final int DRAWER_CLOSE_DELAY_LONG = 400;
private static final String CONTRIBUTE_LINK = "https://github.com/syng-io";
private static final String CONTINUE_SEARCH_LINK = "dapp://syng.io/store?q=search%20query";
private ArrayList<String> mDAppNamesList = new ArrayList<>(Arrays.asList("Console"));
private ArrayList<String> mAccountNamesList = new ArrayList<>(Arrays.asList("Cow"));
private ActionBarDrawerToggle mDrawerToggle;
private Spinner mAccountSpinner;
@ -108,12 +114,6 @@ public abstract class BaseActivity extends AppCompatActivity implements
mDrawerLayout = (DrawerLayout) inflater.inflate(R.layout.drawer, null, false);
FrameLayout content = (FrameLayout) mDrawerLayout.findViewById(R.id.content);
mDAppsRecyclerView = (RecyclerView) mDrawerLayout.findViewById(R.id.dapd_drawer_recycler_view);
mDAppsRecyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager1 = new LinearLayoutManager(this);
mDAppsRecyclerView.setLayoutManager(layoutManager1);
initDApps();
Toolbar toolbar = (Toolbar) inflater.inflate(layoutResID, content, true).findViewById(R.id.myToolbar);
if (toolbar != null) {
setSupportActionBar(toolbar);
@ -137,6 +137,7 @@ public abstract class BaseActivity extends AppCompatActivity implements
mSearchTextView = (EditText) mDrawerLayout.findViewById(R.id.search);
initSearch();
mDrawerLayout.findViewById(R.id.ll_import_wallet).setOnClickListener(this);
mDrawerLayout.findViewById(R.id.ll_settings).setOnClickListener(this);
mDrawerLayout.findViewById(R.id.ll_contribute).setOnClickListener(this);
mDrawerLayout.findViewById(R.id.drawer_header).setOnClickListener(this);
@ -150,6 +151,12 @@ public abstract class BaseActivity extends AppCompatActivity implements
mAccountsRecyclerView.setLayoutManager(layoutManager2);
initProfiles();
mDAppsRecyclerView = (RecyclerView) mDrawerLayout.findViewById(R.id.dapd_drawer_recycler_view);
mDAppsRecyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager1 = new LinearLayoutManager(this);
mDAppsRecyclerView.setLayoutManager(layoutManager1);
initDApps();
ImageView header = (ImageView) mDrawerLayout.findViewById(R.id.iv_header);
Glide.with(this).load(R.drawable.two).into(header);
@ -180,39 +187,40 @@ public abstract class BaseActivity extends AppCompatActivity implements
private void initProfiles() {
mProfiles = new ArrayList<>(mAccountNamesList.size());
for (String name : mAccountNamesList) {
mProfiles = PrefsUtil.getProfiles();
// Add default cow account if not present
if (mProfiles.size() == 0) {
Profile profile = new Profile();
profile.setName(name);
// default cow account
if (name.equals("Cow")) {
// Add default cow and monkey addresses
List<String> addresses = new ArrayList<String>();
byte[] cowAddr = HashUtil.sha3("cow".getBytes());
addresses.add(Hex.toHexString(cowAddr));
String secret = CONFIG.coinbaseSecret();
byte[] cbAddr = HashUtil.sha3(secret.getBytes());
addresses.add(Hex.toHexString(cbAddr));
profile.setPrivateKeys(addresses);
SyngApplication app = (SyngApplication) getApplication();
if (app.currentProfile == null) {
changeProfile(profile);
}
}
profile.setName("Cow");
// Add default cow and monkey addresses
List<String> addresses = new ArrayList<String>();
byte[] cowAddr = HashUtil.sha3("cow".getBytes());
addresses.add(Hex.toHexString(cowAddr));
String secret = CONFIG.coinbaseSecret();
byte[] cbAddr = HashUtil.sha3(secret.getBytes());
addresses.add(Hex.toHexString(cbAddr));
profile.setPrivateKeys(addresses);
PrefsUtil.saveProfile(profile);
mProfiles.add(profile);
}
mAccountDrawerAdapter = new AccountDrawerAdapter(this, mProfiles, this);
mAccountsRecyclerView.setAdapter(mAccountDrawerAdapter);
if (SyngApplication.currentProfile == null) {
SyngApplication.changeProfile(mProfiles.get(0));
}
}
private void initDApps() {
mDApps = new ArrayList<>(mDAppNamesList.size());
for (String name : mDAppNamesList) {
mDApps.add(new Dapp(name));
mDApps = new ArrayList<>();
if (SyngApplication.currentProfile != null) {
mDApps = SyngApplication.currentProfile.getDapps();
}
mDAppsDrawerAdapter = new DAppDrawerAdapter(new ArrayList<>(mDApps), this);
mDAppsRecyclerView.setAdapter(mDAppsDrawerAdapter);
// Add default Console dapp if not present
if (mDApps.size() == 0) {
mDApps.add(new Dapp("Console"));
}
updateAppList(mSearchTextView.getText().toString());
}
@ -226,11 +234,8 @@ public abstract class BaseActivity extends AppCompatActivity implements
if (textView != null) {
textView.setText(profile.getName());
}
SyngApplication application = (SyngApplication) getApplication();
List<String> privateKeys = profile.getPrivateKeys();
SyngApplication.sEthereumConnector.init(privateKeys);
application.currentProfile = profile;
//currentPosition = spinnerAdapter.getPosition(profile);
SyngApplication.changeProfile(profile);
initDApps();
}
protected void requestChangeProfile(Profile profile) {
@ -374,6 +379,9 @@ public abstract class BaseActivity extends AppCompatActivity implements
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.ll_import_wallet:
onProfileImport();
break;
case R.id.ll_contribute:
String url = CONTRIBUTE_LINK;
Intent intent = new Intent(Intent.ACTION_VIEW);
@ -405,6 +413,7 @@ public abstract class BaseActivity extends AppCompatActivity implements
Profile profile = new Profile();
profile.setName(input.toString());
mProfiles.add(profile);
PrefsUtil.saveProfile(profile);
mAccountDrawerAdapter.notifyDataSetChanged();
}
}).show();
@ -501,17 +510,57 @@ public abstract class BaseActivity extends AppCompatActivity implements
}
@Override
public void onDAppPress(final Dapp dapp) {
public void onProfileImport() {
new MaterialDialog.Builder(this)
.title("Edit")
.customView(R.layout.dapp_form, true)
.positiveText(R.string.save)
.title(R.string.wallet_title)
.customView(R.layout.wallet_import, true)
.positiveText(R.string.sImport)
.negativeText(R.string.cancel)
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
Toast.makeText(BaseActivity.this, "Just do nothing", Toast.LENGTH_SHORT).show();
RadioButton importJsonRadio = (RadioButton)dialog.findViewById(R.id.radio_import_json);
EditText importPathEdit = (EditText)dialog.findViewById(R.id.wallet_import_path);
EditText walletPasswordEdit = (EditText)dialog.findViewById(R.id.wallet_password);
String importPath = importPathEdit.getText().toString();
String password = walletPasswordEdit.getText().toString();
String fileContents = null;
try {
File walletFile = new File(importPath);
if (walletFile.exists()) {
FileInputStream stream = new FileInputStream(walletFile);
try {
FileChannel fileChannel = stream.getChannel();
MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
fileContents = Charset.defaultCharset().decode(buffer).toString();
} finally {
stream.close();
}
} else {
Toast.makeText(BaseActivity.this, R.string.file_not_found, Toast.LENGTH_SHORT).show();
logger.warn("Wallet file not found: " + importPath);
return;
}
} catch (Exception e) {
Toast.makeText(BaseActivity.this, R.string.error_reading_file, Toast.LENGTH_SHORT).show();
logger.error("Error reading wallet file", e);
}
if (importJsonRadio.isChecked()) {
if (SyngApplication.currentProfile != null) {
if (SyngApplication.currentProfile.importWallet(fileContents, password)) {
PrefsUtil.updateProfile(SyngApplication.currentProfile);
SyngApplication.changeProfile(SyngApplication.currentProfile);
} else {
Toast.makeText(BaseActivity.this, R.string.invalid_wallet_password, Toast.LENGTH_SHORT).show();
}
} else {
logger.warn("SyngApplication.currentProfile is null ...?!");
}
} else {
SyngApplication.currentProfile.importPrivateKey(fileContents, password);
}
}
@Override
@ -522,9 +571,41 @@ public abstract class BaseActivity extends AppCompatActivity implements
.build().show();
}
@Override
public void onDAppPress(final Dapp dapp) {
MaterialDialog dialog = new MaterialDialog.Builder(this)
.title("Edit")
.customView(R.layout.dapp_form, true)
.positiveText(R.string.save)
.negativeText(R.string.cancel)
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
EditText dappNameEdit = (EditText) dialog.findViewById(R.id.dapp_name);
EditText dappUrlEdit = (EditText) dialog.findViewById(R.id.dapp_url);
dapp.setName(dappNameEdit.getText().toString());
dapp.setUrl(dappUrlEdit.getText().toString());
SyngApplication.updateDapp(dapp);
initDApps();
}
@Override
public void onNegative(MaterialDialog dialog) {
dialog.hide();
}
})
.build();
EditText dappNameEdit = (EditText) dialog.findViewById(R.id.dapp_name);
dappNameEdit.setText(dapp.getName());
EditText dappUrlEdit = (EditText) dialog.findViewById(R.id.dapp_url);
dappUrlEdit.setText(dapp.getUrl());
dialog.show();
}
@Override
public void onProfilePress(Profile profile) {
new MaterialDialog.Builder(this)
MaterialDialog dialog = new MaterialDialog.Builder(this)
.title("Edit account")
.content("Put your name to create new account")
.inputType(InputType.TYPE_CLASS_TEXT)
@ -534,7 +615,7 @@ public abstract class BaseActivity extends AppCompatActivity implements
Toast.makeText(BaseActivity.this, "Just do nothing", Toast.LENGTH_SHORT).show();
}
}).show();
dialog.getInputEditText().setText(profile.getName());
}
@Override
@ -548,7 +629,12 @@ public abstract class BaseActivity extends AppCompatActivity implements
@Override
public void onPositive(MaterialDialog dialog) {
Toast.makeText(BaseActivity.this, "Just do nothing", Toast.LENGTH_SHORT).show();
EditText dappNameEdit = (EditText)dialog.findViewById(R.id.dapp_name);
EditText dappUrlEdit = (EditText)dialog.findViewById(R.id.dapp_url);
Dapp dapp = new Dapp(dappNameEdit.getText().toString());
dapp.setUrl(dappUrlEdit.getText().toString());
SyngApplication.addDapp(dapp);
initDApps();
}
@Override

View File

@ -27,6 +27,7 @@ public class AccountDrawerAdapter extends RecyclerView.Adapter<RecyclerView.View
void onProfileClick(Profile profile);
void onProfilePress(Profile profile);
void onProfileImport();
void onNewProfile();
}

View File

@ -9,6 +9,9 @@ import com.squareup.leakcanary.RefWatcher;
import org.ethereum.android.service.ConnectorHandler;
import org.ethereum.android.service.EthereumConnector;
import java.util.List;
import io.syng.entity.Dapp;
import io.syng.entity.Profile;
import io.syng.service.EthereumService;
import io.syng.util.PrefsUtil;
@ -20,7 +23,23 @@ public class SyngApplication extends MultiDexApplication implements ConnectorHan
private RefWatcher refWatcher;
public Profile currentProfile;
public static Profile currentProfile;
public static void changeProfile(Profile profile) {
List<String> privateKeys = profile.getPrivateKeys();
sEthereumConnector.init(privateKeys);
currentProfile = profile;
}
public static void addDapp(Dapp dapp) {
currentProfile.addDapp(dapp);
PrefsUtil.updateProfile(currentProfile);
}
public static void updateDapp(Dapp dapp) {
currentProfile.updateDapp(dapp);
PrefsUtil.updateProfile(currentProfile);
}
@Override
public void onCreate() {

View File

@ -1,22 +1,40 @@
package io.syng.entity;
public class Dapp {
import org.ethereum.crypto.HashUtil;
import org.spongycastle.util.encoders.Hex;
import java.io.Serializable;
public class Dapp implements Serializable {
protected String name = "";
protected String version = "";
protected String url = "";
protected String id;
protected String id = "";
private static final long serialVersionUID = 1L;
public Dapp(String id, String name) {
this.id = id;
this.name = name;
}
public Dapp(String name) {
this.name = name;
this.id = generateID();
}
public Dapp() {
this.id = generateID();
}
protected String generateID() {
byte[] privateKey = HashUtil.sha3(HashUtil.randomPeerId());
return Hex.toHexString(privateKey);
}
public String getName() {

View File

@ -1,6 +1,12 @@
package io.syng.entity;
import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.ethereum.wallet.EtherSaleWallet;
import org.ethereum.wallet.EtherSaleWalletDecoder;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.io.Serializable;
@ -9,6 +15,8 @@ import java.util.List;
public class Profile implements Serializable {
private static final Logger logger = LoggerFactory.getLogger("Profile");
protected String name;
protected List<String> privateKeys = new ArrayList<>();
@ -16,7 +24,7 @@ public class Profile implements Serializable {
/* "password protect profile" (encrypt the private keys) */
protected boolean passwordProtectedProfile = false;
protected List<Dapp> dapps;
protected List<Dapp> dapps = new ArrayList<>();
private static final long serialVersionUID = 1L;
@ -80,6 +88,16 @@ public class Profile implements Serializable {
this.dapps.add(dapp);
}
public void updateDapp(Dapp dapp) {
for (Dapp item: dapps) {
if (item.getId().equals(dapp.getId())) {
int index = dapps.indexOf(item);
dapps.set(index, dapp);
}
}
}
public void removeDapp(Dapp dapp) {
this.dapps.remove(dapp);
@ -150,4 +168,50 @@ public class Profile implements Serializable {
// TODO: Decrypt private key
return privateKey;
}
public boolean importWallet(String jsonWallet, String password) {
try {
JSONObject json = new JSONObject(jsonWallet);
byte[] privateKey = null;
EtherSaleWallet wallet = new EtherSaleWallet();
if (json.has("encseed")) {
wallet.setEncseed(json.getString("encseed"));
wallet.setEthaddr(json.getString("ethaddr"));
wallet.setEmail(json.getString("email"));
wallet.setBtcaddr(json.getString("btcaddr"));
EtherSaleWalletDecoder decoder = new EtherSaleWalletDecoder(wallet);
privateKey = decoder.getPrivateKey(password);
} else if (json.has("Crypto")) {
wallet.setEncseed(json.getJSONObject("Crypto").getJSONObject("cipherparams").getString("iv") + json.getJSONObject("Crypto").getString("ciphertext"));
wallet.setEthaddr(json.getString("address"));
EtherSaleWalletDecoder decoder = new EtherSaleWalletDecoder(wallet);
privateKey = decoder.getPrivateKey(password);
}
if (privateKey == null) {
logger.warn("Invalid json wallet file.");
return false;
}
ECKey key = ECKey.fromPrivate(privateKey);
String address = Hex.toHexString(key.getAddress());
if (address.equals(wallet.getEthaddr())) {
privateKeys.add(Hex.toHexString(privateKey));
} else {
logger.warn("Invalid wallet password.");
return false;
}
} catch (Exception e) {
logger.error("Error importing wallet.", e);
return false;
}
return true;
}
public void importPrivateKey(String privateKey, String password) {
privateKeys.add(decryptPrivateKey(privateKey, password));
}
}

View File

@ -39,7 +39,7 @@ public final class PrefsUtil {
}
private static Editor getEditor() {
return getInstance().mPreferences.edit();
return getPrefs().edit();
}
private static SharedPreferences getPrefs() {
@ -48,11 +48,12 @@ public final class PrefsUtil {
public static void saveProfiles(ArrayList<Profile> profiles) {
try {
getEditor().putString(PROFILES_KEY, ObjectSerializer.serialize(profiles));
SharedPreferences.Editor editor = getEditor();
editor.putString(PROFILES_KEY, ObjectSerializer.serialize(profiles));
editor.apply();
} catch (Exception e) {
e.printStackTrace();
}
getEditor().apply();
}
public static ArrayList<Profile> getProfiles() {
@ -65,6 +66,32 @@ public final class PrefsUtil {
return profiles;
}
public static void updateProfile(Profile profile) {
ArrayList<Profile> profiles = getProfiles();
for (Profile item: profiles) {
if (item.getName().equals(profile.getName())) {
int index = profiles.indexOf(item);
profiles.set(index, profile);
saveProfiles(profiles);
break;
}
}
}
public static boolean saveProfile(Profile profile) {
ArrayList<Profile> profiles = PrefsUtil.getProfiles();
for (Profile item: profiles) {
if (item.getName().equals(profile.getName())) {
return false;
}
}
profiles.add(profile);
saveProfiles(profiles);
return true;
}
public static void setFirstLaunch(boolean isFirstLaunch) {
getEditor().putBoolean(FIRST_LAUNCH_KEY, isFirstLaunch).apply();

View File

@ -17,6 +17,35 @@
android:orientation="vertical"
>
<LinearLayout
android:id="@+id/ll_import_wallet"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:src="@drawable/ic_add_black_24dp"
android:tint="@color/drawer_icons_color"/>
<TextView
android:id="@+id/tv_import_wallet"
style="@style/Base.TextAppearance.AppCompat.Body2"
android:layout_width="match_parent"
android:layout_height="?attr/listPreferredItemHeightSmall"
android:gravity="center_vertical"
android:paddingLeft="32dp"
android:paddingStart="32dp"
android:text="Import Wallet"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/ll_settings"
android:layout_width="match_parent"

View File

@ -2,33 +2,36 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<RadioButton android:id="@+id/radio_new_account"
<RadioButton android:id="@+id/radio_import_json"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/accent"
android:text="@string/create_new_wallet"/>
<RadioButton android:id="@+id/radio_import_file"
android:checked="true"
android:text="@string/json_wallet"/>
<RadioButton android:id="@+id/radio_import_hex"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/accent"
android:text="@string/import_wallet_from_file"/>
<RadioButton android:id="@+id/radio_import_string"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/accent"
android:text="@string/import_account_from_string"/>
android:text="@string/hex_private_key"/>
</RadioGroup>
<EditText
android:id="@+id/accout_import_source"
android:id="@+id/wallet_import_path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="@color/accent"
android:inputType="none"/>
android:singleLine="true"
android:hint="File Path"/>
<EditText
android:id="@+id/wallet_password"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="@color/accent"
android:singleLine="true"
android:hint="Password"/>
</LinearLayout>

View File

@ -15,15 +15,18 @@
<string name="url">Url</string>
<string name="save">Save</string>
<string name="cancel">Cancel</string>
<string name="sImport">Import</string>
<string name="manage_profiles">Manage Profiles</string>
<string name="title_activity_spash_screen">Syng</string>
<string name="wallet_title">Wallet</string>
<string name="wallet_title">Import Wallet</string>
<string name="ok">OK</string>
<string name="create_new_wallet">Create new Wallet</string>
<string name="import_wallet_from_file">Import wallet from file</string>
<string name="import_account_from_string">Import account from string</string>
<string name="json_wallet">Import json wallet</string>
<string name="hex_private_key">Import hex encoded private key</string>
<string name="file_not_found">The specified file was not found.</string>
<string name="error_reading_file">Error reading specified file.</string>
<string name="invalid_wallet_password">Invalid wallet or password.</string>
<string name="request_profile_password">Profile Password</string>
<string name="warning.message">This is PRE-ALPHA software and most likely contains numerous bugs. The layout, design, and functionality may change frequently. This also lacks numerous features that will be included in later versions. Using this software you agree that you hold yourself personally accountable for interacting with this software.</string>