diff --git a/app/build.gradle b/app/build.gradle index 34c1bfc..d2f0cc6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,4 +52,7 @@ dependencies { compile 'org.bitcoinj:bitcoinj-core:0.14.3' compile 'com.neovisionaries:nv-websocket-client:1.30' compile 'org.tukaani:xz:1.6' + compile 'com.jakewharton:butterknife:8.8.1' + apt 'com.jakewharton:butterknife-compiler:8.8.1' + } diff --git a/app/src/androidTest/java/carbon/crypto/com/carbon/TransactionListTest.java b/app/src/androidTest/java/carbon/crypto/com/carbon/TransactionListTest.java index 60f8a33..07089cf 100644 --- a/app/src/androidTest/java/carbon/crypto/com/carbon/TransactionListTest.java +++ b/app/src/androidTest/java/carbon/crypto/com/carbon/TransactionListTest.java @@ -10,13 +10,10 @@ import android.support.test.InstrumentationRegistry; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; import java.util.List; import carbon.crypto.com.carbon.Assertions.RecyclerViewItemsCountAssertion; -import cy.agorise.crystalwallet.IntroActivity; +import cy.agorise.crystalwallet.activities.IntroActivity; import cy.agorise.crystalwallet.R; import cy.agorise.crystalwallet.dao.CrystalDatabase; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; @@ -25,9 +22,6 @@ import cy.agorise.crystalwallet.randomdatagenerators.RandomTransactionsGenerator import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.matcher.ViewMatchers.withId; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - /** * Created by Henry Varona on 19/9/2017. diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 04eb050..716cd8e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,13 +10,17 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - + + + + + \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/AccountSeedsManagementActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/AccountSeedsManagementActivity.java new file mode 100644 index 0000000..53b1170 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/AccountSeedsManagementActivity.java @@ -0,0 +1,59 @@ +package cy.agorise.crystalwallet.activities; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.widget.Button; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.viewmodels.AccountSeedListViewModel; +import cy.agorise.crystalwallet.views.AccountSeedListView; + +public class AccountSeedsManagementActivity extends AppCompatActivity { + + AccountSeedsManagementActivity accountSeedsManagementActivity; + AccountSeedListViewModel accountSeedListViewModel; + + @BindView(R.id.vAccountSeedList) + AccountSeedListView vAccountSeedList; + + @BindView(R.id.btnImportAccountSeed) + Button btnImportAccountSeed; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.account_seeds_management); + ButterKnife.bind(this); + + accountSeedListViewModel = ViewModelProviders.of(this).get(AccountSeedListViewModel.class); + LiveData> accountSeedData = accountSeedListViewModel.getAccountSeedList(); + //vAccountSeedList = this.findViewById(R.id.vAccountSeedList); + + accountSeedData.observe(this, new Observer>() { + @Override + public void onChanged(List accountSeeds) { + vAccountSeedList.setData(accountSeeds); + } + }); + + //accountSeedListView.setData(null); + } + + @OnClick (R.id.btnImportAccountSeed) + public void importAccountSeed(){ + Intent intent = new Intent(this, ImportSeedActivity.class); + startActivity(intent); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/ImportSeedActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/ImportSeedActivity.java new file mode 100644 index 0000000..7bc0701 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/ImportSeedActivity.java @@ -0,0 +1,60 @@ +package cy.agorise.crystalwallet.activities; + +import android.arch.lifecycle.LifecycleActivity; +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.widget.Button; +import android.widget.TextView; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.viewmodels.AccountSeedListViewModel; +import cy.agorise.crystalwallet.viewmodels.AccountSeedViewModel; +import cy.agorise.crystalwallet.viewmodels.TransactionListViewModel; +import cy.agorise.crystalwallet.views.TransactionListView; + +public class ImportSeedActivity extends AppCompatActivity { + + AccountSeedViewModel accountSeedViewModel; + + @BindView(R.id.tvPin) + TextView tvPin; + + @BindView(R.id.tvPinConfirmation) + TextView tvPinConfirmation; + + @BindView(R.id.etSeedWords) + TextView etSeedWords; + + @BindView(R.id.btnImport) + Button btnImport; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.import_seed); + + ButterKnife.bind(this); + + accountSeedViewModel = ViewModelProviders.of(this).get(AccountSeedViewModel.class); + //this.seed = new AccountSeed(); + } + + @OnClick(R.id.btnImport) + public void importSeed(){ + AccountSeed seed = new AccountSeed(); + + //TODO verify if PIN and PIN confirmation are not null and are the same + //TODO verify if words are already in the db + seed.setMasterSeed(etSeedWords.getText().toString()); + + accountSeedViewModel.addSeed(seed); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/IntroActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java similarity index 78% rename from app/src/main/java/cy/agorise/crystalwallet/IntroActivity.java rename to app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java index 19acc98..382fdd4 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/IntroActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java @@ -1,4 +1,4 @@ -package cy.agorise.crystalwallet; +package cy.agorise.crystalwallet.activities; import android.arch.lifecycle.LifecycleActivity; import android.arch.lifecycle.LiveData; @@ -6,6 +6,7 @@ import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProvider; import android.arch.lifecycle.ViewModelProviders; import android.arch.paging.PagedList; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; @@ -14,6 +15,7 @@ import android.widget.Button; import java.util.List; +import cy.agorise.crystalwallet.R; import cy.agorise.crystalwallet.dao.CrystalDatabase; import cy.agorise.crystalwallet.models.AccountSeed; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; @@ -21,12 +23,13 @@ import cy.agorise.crystalwallet.models.CryptoNetAccount; import cy.agorise.crystalwallet.randomdatagenerators.RandomCryptoNetAccountGenerator; import cy.agorise.crystalwallet.randomdatagenerators.RandomSeedGenerator; import cy.agorise.crystalwallet.randomdatagenerators.RandomTransactionsGenerator; +import cy.agorise.crystalwallet.viewmodels.AccountSeedListViewModel; import cy.agorise.crystalwallet.viewmodels.TransactionListViewModel; import cy.agorise.crystalwallet.views.TransactionListView; import static cy.agorise.crystalwallet.R.string.transactions; -public class IntroActivity extends LifecycleActivity { +public class IntroActivity extends AppCompatActivity { TransactionListViewModel transactionListViewModel; TransactionListView transactionListView; @@ -36,6 +39,16 @@ public class IntroActivity extends LifecycleActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_intro); + //Checks if the user has any seed created + AccountSeedListViewModel accountSeedListViewModel = ViewModelProviders.of(this).get(AccountSeedListViewModel.class); + + if (accountSeedListViewModel.accountSeedsCount() == 0){ + //If the user doesn't have any seeds created, then + //send the user to create/import an account + Intent intent = new Intent(this, AccountSeedsManagementActivity.class); + startActivity(intent); + } + /*CrystalDatabase db = CrystalDatabase.getAppDatabase(getApplicationContext()); List seeds = RandomSeedGenerator.generateSeeds(2); for(int i=0;i> transactionData = transactionListViewModel.getTransactionList(); @@ -64,6 +77,6 @@ public class IntroActivity extends LifecycleActivity { public void onChanged(PagedList cryptoCoinTransactions) { transactionListView.setData(cryptoCoinTransactions); } - }); + });*/ } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/AccountSeedDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/AccountSeedDao.java index 47e2102..f3e4433 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/AccountSeedDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/AccountSeedDao.java @@ -1,5 +1,7 @@ package cy.agorise.crystalwallet.dao; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; @@ -17,9 +19,17 @@ import cy.agorise.crystalwallet.models.AccountSeed; public interface AccountSeedDao { @Query("SELECT * FROM account_seed") - List getAll(); + LiveData> getAll(); + + @Query("SELECT * FROM account_seed WHERE id = :id") + LiveData findById(long id); + + @Query("SELECT COUNT(*) from account_seed") + int countAccountSeeds(); @Insert(onConflict = OnConflictStrategy.REPLACE) - public long[] insertAccountSeed(AccountSeed... seeds); + public long[] insertAccountSeeds(AccountSeed... seeds); + @Insert(onConflict = OnConflictStrategy.REPLACE) + public long insertAccountSeed(AccountSeed seed); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/converters/Converters.java b/app/src/main/java/cy/agorise/crystalwallet/dao/converters/Converters.java index 020933c..9fa6cb4 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/converters/Converters.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/converters/Converters.java @@ -6,6 +6,7 @@ import java.util.Date; import cy.agorise.crystalwallet.enums.CryptoCoin; import cy.agorise.crystalwallet.enums.CryptoNet; +import cy.agorise.crystalwallet.enums.SeedType; import cy.agorise.crystalwallet.models.CryptoNetAccount; import static cy.agorise.crystalwallet.R.string.account; @@ -84,4 +85,22 @@ public class Converters { return CryptoNet.valueOf(value); } } + + @TypeConverter + public String seedTypeToName(SeedType value) { + if (value == null) { + return ""; + } else { + return value.name(); + } + } + + @TypeConverter + public SeedType nameToSeedType(String value) { + if (value.equals("")){ + return null; + } else { + return SeedType.valueOf(value); + } + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/enums/SeedType.java b/app/src/main/java/cy/agorise/crystalwallet/enums/SeedType.java new file mode 100644 index 0000000..505a13f --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/enums/SeedType.java @@ -0,0 +1,10 @@ +package cy.agorise.crystalwallet.enums; + +/** + * Created by Henry Varona on 30/9/2017. + */ + +public enum SeedType { + BIP39, + BRAINKEY +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/AccountSeed.java b/app/src/main/java/cy/agorise/crystalwallet/models/AccountSeed.java index b00cbf4..40294d4 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/AccountSeed.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/AccountSeed.java @@ -4,6 +4,10 @@ package cy.agorise.crystalwallet.models; import android.arch.persistence.room.ColumnInfo; import android.arch.persistence.room.Entity; import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; +import android.support.v7.recyclerview.extensions.DiffCallback; + +import cy.agorise.crystalwallet.enums.SeedType; /** * Represents a type of crypto seed for HD wallets @@ -32,6 +36,11 @@ public class AccountSeed { @ColumnInfo(name = "master_seed") private String mMasterSeed; + /** + * The type of this seed: BIP39, BRAINKEY + */ + private SeedType type; + public long getId() { return mId; } @@ -56,5 +65,36 @@ public class AccountSeed { this.mMasterSeed = mMasterSeed; } + public SeedType getType() { + return type; + } + public void setType(SeedType type) { + this.type = type; + } + + public static final DiffCallback DIFF_CALLBACK = new DiffCallback() { + @Override + public boolean areItemsTheSame( + @NonNull AccountSeed oldAccountSeed, @NonNull AccountSeed newAccountSeed) { + return oldAccountSeed.getId() == newAccountSeed.getId(); + } + @Override + public boolean areContentsTheSame( + @NonNull AccountSeed oldAccountSeed, @NonNull AccountSeed newAccountSeed) { + return oldAccountSeed.equals(newAccountSeed); + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AccountSeed that = (AccountSeed) o; + + if (mId != that.mId) return false; + return mMasterSeed.equals(that.mMasterSeed); + + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/AccountSeedListViewModel.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/AccountSeedListViewModel.java new file mode 100644 index 0000000..b1b9ae6 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/AccountSeedListViewModel.java @@ -0,0 +1,36 @@ +package cy.agorise.crystalwallet.viewmodels; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.paging.PagedList; + +import java.util.List; + +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.models.CryptoCoinTransaction; + +/** + * Created by Henry Varona on 27/9/2017. + */ + +public class AccountSeedListViewModel extends AndroidViewModel { + + private LiveData> accountSeedList; + private CrystalDatabase db; + + public AccountSeedListViewModel(Application application) { + super(application); + this.db = CrystalDatabase.getAppDatabase(application.getApplicationContext()); + this.accountSeedList = this.db.accountSeedDao().getAll(); + } + + public LiveData> getAccountSeedList(){ + return this.accountSeedList; + } + + public int accountSeedsCount(){ + return this.db.accountSeedDao().countAccountSeeds(); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/AccountSeedViewModel.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/AccountSeedViewModel.java new file mode 100644 index 0000000..c36f4d4 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/AccountSeedViewModel.java @@ -0,0 +1,38 @@ +package cy.agorise.crystalwallet.viewmodels; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.MutableLiveData; + +import java.util.List; + +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.AccountSeed; + +/** + * Created by Henry Varona on 27/9/2017. + */ + +public class AccountSeedViewModel extends AndroidViewModel { + + private LiveData accountSeed; + private CrystalDatabase db; + + public AccountSeedViewModel(Application application) { + super(application); + this.db = CrystalDatabase.getAppDatabase(application.getApplicationContext()); + } + + public void loadSeed(int seedId){ + this.accountSeed = this.db.accountSeedDao().findById(seedId); + } + + public void addSeed(AccountSeed seed){ + this.db.accountSeedDao().insertAccountSeed(seed); + } + + public LiveData getAccountSeed(){ + return this.accountSeed; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedListAdapter.java b/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedListAdapter.java new file mode 100644 index 0000000..8208deb --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedListAdapter.java @@ -0,0 +1,40 @@ +package cy.agorise.crystalwallet.views; + + +import android.arch.paging.PagedListAdapter; +import android.support.v7.recyclerview.extensions.ListAdapter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.models.CryptoCoinTransaction; + +/** + * Created by Henry Varona on 11/9/2017. + */ + +public class AccountSeedListAdapter extends ListAdapter { + + public AccountSeedListAdapter() { + super(AccountSeed.DIFF_CALLBACK); + } + + @Override + public AccountSeedViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.account_seed_list_item,parent,false); + + return new AccountSeedViewHolder(v); + } + + @Override + public void onBindViewHolder(AccountSeedViewHolder holder, int position) { + AccountSeed accountSeed = getItem(position); + if (accountSeed != null) { + holder.bindTo(accountSeed); + } else { + holder.clear(); + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedListView.java b/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedListView.java new file mode 100644 index 0000000..2e8ea55 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedListView.java @@ -0,0 +1,73 @@ +package cy.agorise.crystalwallet.views; + +import android.arch.paging.PagedList; +import android.content.Context; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.RelativeLayout; + +import java.util.List; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.models.CryptoCoinTransaction; +import cy.agorise.crystalwallet.viewmodels.AccountSeedListViewModel; +import cy.agorise.crystalwallet.viewmodels.TransactionListViewModel; + +/** + * Created by Henry Varona on 10/9/2017. + */ + +public class AccountSeedListView extends RelativeLayout { + + LayoutInflater mInflater; + + View rootView; + RecyclerView listView; + AccountSeedListAdapter listAdapter; + + AccountSeedListViewModel accountSeedListViewModel; + + public AccountSeedListView(Context context){ + super(context); + this.mInflater = LayoutInflater.from(context); + init(); + } + + public AccountSeedListView(Context context, AttributeSet attrs) { + super(context, attrs); + this.mInflater = LayoutInflater.from(context); + init(); + } + + public AccountSeedListView(Context context, AttributeSet attrs, int defStyle){ + super(context, attrs, defStyle); + this.mInflater = LayoutInflater.from(context); + init(); + } + + public void init(){ + rootView = mInflater.inflate(R.layout.account_seed_list, this, true); + this.listView = rootView.findViewById(R.id.accountSeedListView); + + final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this.getContext()); + this.listView.setLayoutManager(linearLayoutManager); + this.listView.setNestedScrollingEnabled(false); + } + + public void setData(List data){ + if (this.listAdapter == null) { + this.listAdapter = new AccountSeedListAdapter(); + this.listView.setAdapter(this.listAdapter); + } + + if (data != null) { + this.listAdapter.setList(data); + } + } + + +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedViewHolder.java new file mode 100644 index 0000000..1b0389c --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/AccountSeedViewHolder.java @@ -0,0 +1,34 @@ +package cy.agorise.crystalwallet.views; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.models.CryptoCoinTransaction; + +/** + * Created by Henry Varona on 17/9/2017. + */ + +public class AccountSeedViewHolder extends RecyclerView.ViewHolder { + private TextView tvAccountSeedName; + + public AccountSeedViewHolder(View itemView) { + super(itemView); + tvAccountSeedName = (TextView) itemView.findViewById(R.id.tvAccountSeedName); + } + + public void clear(){ + tvAccountSeedName.setText("loading..."); + } + + public void bindTo(final AccountSeed accountSeed) { + if (accountSeed == null){ + tvAccountSeedName.setText("loading..."); + } else { + tvAccountSeedName.setText(accountSeed.getName()); + } + } +} diff --git a/app/src/main/res/layout/account_seed_list.xml b/app/src/main/res/layout/account_seed_list.xml new file mode 100644 index 0000000..ca509d3 --- /dev/null +++ b/app/src/main/res/layout/account_seed_list.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/account_seed_list_item.xml b/app/src/main/res/layout/account_seed_list_item.xml new file mode 100644 index 0000000..ed9e854 --- /dev/null +++ b/app/src/main/res/layout/account_seed_list_item.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/account_seeds_management.xml b/app/src/main/res/layout/account_seeds_management.xml new file mode 100644 index 0000000..c55d574 --- /dev/null +++ b/app/src/main/res/layout/account_seeds_management.xml @@ -0,0 +1,20 @@ + + + +