diff --git a/app/build.gradle b/app/build.gradle index c79b2db..44b82fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -63,4 +63,6 @@ dependencies { annotationProcessor 'android.arch.lifecycle:compiler:1.0.0' annotationProcessor 'android.arch.persistence.room:compiler:1.0.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' + + compile 'com.squareup.picasso:picasso:2.5.2' } diff --git a/app/schemas/cy.agorise.crystalwallet.dao.CrystalDatabase/2.json b/app/schemas/cy.agorise.crystalwallet.dao.CrystalDatabase/2.json index 2f9409f..22f45c4 100644 --- a/app/schemas/cy.agorise.crystalwallet.dao.CrystalDatabase/2.json +++ b/app/schemas/cy.agorise.crystalwallet.dao.CrystalDatabase/2.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 2, - "identityHash": "a44ccb96c8213951403ed2a283fb3367", + "identityHash": "22cb2a56b28a9f7088ec98d6a72f9f67", "entities": [ { "tableName": "account_seed", @@ -235,7 +235,7 @@ }, { "tableName": "contact", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `gravatar` TEXT)", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `email` TEXT, `gravatar` TEXT)", "fields": [ { "fieldPath": "mId", @@ -249,6 +249,12 @@ "affinity": "TEXT", "notNull": false }, + { + "fieldPath": "mEmail", + "columnName": "email", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "mGravatar", "columnName": "gravatar", @@ -278,6 +284,70 @@ "name" ], "createSql": "CREATE UNIQUE INDEX `index_contact_name` ON `${TABLE_NAME}` (`name`)" + }, + { + "name": "index_contact_email", + "unique": false, + "columnNames": [ + "email" + ], + "createSql": "CREATE INDEX `index_contact_email` ON `${TABLE_NAME}` (`email`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "contact_address", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `contact_id` INTEGER NOT NULL, `crypto_net` TEXT NOT NULL, `address` TEXT)", + "fields": [ + { + "fieldPath": "mId", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mContactId", + "columnName": "contact_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "mCryptoNet", + "columnName": "crypto_net", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "mAddress", + "columnName": "address", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_contact_address_id", + "unique": true, + "columnNames": [ + "id" + ], + "createSql": "CREATE UNIQUE INDEX `index_contact_address_id` ON `${TABLE_NAME}` (`id`)" + }, + { + "name": "index_contact_address_contact_id_crypto_net", + "unique": true, + "columnNames": [ + "contact_id", + "crypto_net" + ], + "createSql": "CREATE UNIQUE INDEX `index_contact_address_contact_id_crypto_net` ON `${TABLE_NAME}` (`contact_id`, `crypto_net`)" } ], "foreignKeys": [] @@ -622,7 +692,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"a44ccb96c8213951403ed2a283fb3367\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"22cb2a56b28a9f7088ec98d6a72f9f67\")" ] } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b3ebbef..1eabefc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,11 +1,12 @@ + + android:theme="@style/AppTheme.NoActionBar" > @@ -37,20 +38,27 @@ - + - + + + - + + + \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java index 9ef1161..95c7fb4 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java @@ -204,6 +204,12 @@ public class BoardActivity extends AppCompatActivity { } } + @OnClick(R.id.fabAddContact) + public void beginCreateContact(){ + Intent intent = new Intent(this, CreateContactActivity.class); + startActivity(intent); + } + /* * dispatch the user to the receive fragment using this account */ @@ -215,8 +221,16 @@ public class BoardActivity extends AppCompatActivity { } ft.addToBackStack(null); + long receiveCryptoNetAccountId = -1; + if (this.cryptoNetAccountId != -1){ + receiveCryptoNetAccountId = this.cryptoNetAccountId; + } else { + CryptoNetBalanceListViewModel cryptoNetBalanceListViewModel = ViewModelProviders.of(this).get(CryptoNetBalanceListViewModel.class); + receiveCryptoNetAccountId = cryptoNetBalanceListViewModel.getFirstBitsharesAccountId(); + } + // Create and show the dialog. - ReceiveTransactionFragment newFragment = ReceiveTransactionFragment.newInstance(this.cryptoNetAccountId); + ReceiveTransactionFragment newFragment = ReceiveTransactionFragment.newInstance(receiveCryptoNetAccountId); newFragment.show(ft, "ReceiveDialog"); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/CreateContactActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/CreateContactActivity.java new file mode 100644 index 0000000..fcb1469 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/CreateContactActivity.java @@ -0,0 +1,254 @@ +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.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnTextChanged; +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.cryptonetinforequests.CryptoNetInfoRequestListener; +import cy.agorise.crystalwallet.cryptonetinforequests.CryptoNetInfoRequests; +import cy.agorise.crystalwallet.cryptonetinforequests.ValidateCreateBitsharesAccountRequest; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.ContactAddress; +import cy.agorise.crystalwallet.models.GrapheneAccount; +import cy.agorise.crystalwallet.viewmodels.ContactListViewModel; +import cy.agorise.crystalwallet.viewmodels.ContactViewModel; +import cy.agorise.crystalwallet.viewmodels.validators.CreateContactValidator; +import cy.agorise.crystalwallet.viewmodels.validators.CreateSeedValidator; +import cy.agorise.crystalwallet.viewmodels.validators.ModifyContactValidator; +import cy.agorise.crystalwallet.viewmodels.validators.UIValidatorListener; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.ValidationField; +import cy.agorise.crystalwallet.views.ContactAddressListAdapter; +import cy.agorise.crystalwallet.views.ContactViewHolder; + +public class CreateContactActivity extends AppCompatActivity implements UIValidatorListener { + + @BindView(R.id.etName) + EditText etName; + @BindView(R.id.tvNameError) + TextView tvNameError; + @BindView(R.id.etEmail) + EditText etEmail; + @BindView(R.id.tvEmailError) + TextView tvEmailError; + @BindView(R.id.btnCancel) + Button btnCancel; + @BindView(R.id.btnCreate) + Button btnCreate; + @BindView(R.id.btnModify) + Button btnModify; + @BindView(R.id.rvContactAddresses) + RecyclerView rvContactAddresses; + @BindView(R.id.btnAddAddress) + Button btnAddAddress; + List contactAddressList; + ContactAddressListAdapter listAdapter; + + CreateContactValidator createContactValidator; + ModifyContactValidator modifyContactValidator; + + Contact contact; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_create_contact); + ButterKnife.bind(this); + + btnCreate.setEnabled(false); + + listAdapter = new ContactAddressListAdapter(); + rvContactAddresses.setLayoutManager(new LinearLayoutManager(getApplicationContext())); + rvContactAddresses.setAdapter(listAdapter); + + long contactId = this.getIntent().getLongExtra("CONTACT_ID",-1); + + btnCreate.setVisibility(View.GONE); + btnModify.setVisibility(View.GONE); + + if (contactId >= 0){ + final ContactViewModel contactViewModel = ViewModelProviders.of(this).get(ContactViewModel.class); + + contactViewModel.init(contactId); + LiveData contactLiveData = contactViewModel.getContact(); + final CreateContactActivity thisActivity = this; + + contactLiveData.observe(this, new Observer() { + @Override + public void onChanged(@Nullable Contact contactChanged) { + if (contactChanged != null){ + contact = contactChanged; + etName.setText(contact.getName()); + etEmail.setText(contact.getEmail()); + + LiveData> contactAddresses = contactViewModel.getContactAddresses(); + + contactAddresses.observe(thisActivity, new Observer>() { + @Override + public void onChanged(@Nullable List contactAddresses) { + contactAddressList = contactAddresses; + listAdapter.setList(contactAddressList); + listAdapter.notifyDataSetChanged(); + } + }); + + modifyContactValidator = new ModifyContactValidator(thisActivity.getApplicationContext(),contact,etName,etEmail); + modifyContactValidator.setListener(thisActivity); + btnModify.setVisibility(View.VISIBLE); + } else { + //No contact was found, this will exit the activity + finish(); + } + } + }); + } else { + contactAddressList = new ArrayList(); + listAdapter.setList(contactAddressList); + createContactValidator = new CreateContactValidator(this.getApplicationContext(),etName,etEmail); + createContactValidator.setListener(this); + + btnCreate.setVisibility(View.VISIBLE); + } + } + + public void validate(){ + if (this.createContactValidator != null){ + this.createContactValidator.validate(); + } else if (this.modifyContactValidator != null){ + this.modifyContactValidator.validate(); + } + } + + public boolean isValid(){ + if (this.createContactValidator != null){ + return this.createContactValidator.isValid(); + } else if (this.modifyContactValidator != null){ + return this.modifyContactValidator.isValid(); + } + + return false; + } + + @OnTextChanged(value = R.id.etName, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterContactNameChanged(Editable editable) { + this.validate(); + } + + @OnTextChanged(value = R.id.etEmail, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterEmailChanged(Editable editable) { + this.validate(); + } + + + @OnClick(R.id.btnAddAddress) + public void addAddress(){ + ContactAddress newContactAddress = new ContactAddress(); + this.contactAddressList.add(newContactAddress); + this.listAdapter.notifyDataSetChanged(); + } + + @OnClick(R.id.btnCancel) + public void cancel(){ + this.finish(); + } + + @OnClick(R.id.btnModify) + public void modifyContact(){ + if (this.modifyContactValidator.isValid()) { + this.contact.setName(etName.getText().toString()); + this.contact.setEmail(etEmail.getText().toString()); + this.contact.clearAddresses(); + + for (ContactAddress contactAddress : contactAddressList){ + this.contact.addAddress(contactAddress); + } + + ContactViewModel contactViewModel = ViewModelProviders.of(this).get(ContactViewModel.class); + if (contactViewModel.modifyContact(this.contact)){ + this.finish(); + } else { + this.modifyContactValidator.validate(); + } + } + } + + @OnClick(R.id.btnCreate) + public void createContact(){ + if (this.createContactValidator.isValid()) { + Contact newContact = new Contact(); + newContact.setName(etName.getText().toString()); + newContact.setEmail(etEmail.getText().toString()); + + for (ContactAddress contactAddress : contactAddressList){ + newContact.addAddress(contactAddress); + } + + ContactViewModel contactViewModel = ViewModelProviders.of(this).get(ContactViewModel.class); + if (contactViewModel.addContact(newContact)){ + this.finish(); + } else { + createContactValidator.validate(); + } + } + } + + @Override + public void onValidationSucceeded(final ValidationField field) { + final CreateContactActivity activity = this; + + activity.runOnUiThread(new Runnable() { + public void run() { + + if (field.getView() == etName) { + tvNameError.setText(""); + } else if (field.getView() == etEmail) { + tvEmailError.setText(""); + } + + if (activity.isValid()){ + btnCreate.setEnabled(true); + } else { + btnCreate.setEnabled(false); + } + } + }); + } + + @Override + public void onValidationFailed(final ValidationField field) { + runOnUiThread(new Runnable() { + + @Override + public void run() { + if (field.getView() == etName) { + tvNameError.setText(field.getMessage()); + } else if (field.getView() == etEmail) { + tvEmailError.setText(field.getMessage()); + } + } + }); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java index fca4558..eafa3e2 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java @@ -1,11 +1,6 @@ package cy.agorise.crystalwallet.activities; -import android.arch.lifecycle.LifecycleActivity; -import android.arch.lifecycle.LiveData; -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.media.MediaPlayer; import android.os.Bundle; @@ -34,12 +29,11 @@ import cy.agorise.crystalwallet.randomdatagenerators.RandomCryptoCoinBalanceGene import cy.agorise.crystalwallet.randomdatagenerators.RandomCryptoNetAccountGenerator; import cy.agorise.crystalwallet.randomdatagenerators.RandomSeedGenerator; import cy.agorise.crystalwallet.randomdatagenerators.RandomTransactionsGenerator; +import cy.agorise.crystalwallet.application.CrystalSecurityMonitor; 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 AppCompatActivity { TransactionListViewModel transactionListViewModel; @@ -82,6 +76,9 @@ public class IntroActivity extends AppCompatActivity { } }); + this.getApplication().registerActivityLifecycleCallbacks(new CrystalSecurityMonitor(this)); + + //Checks if the user has any seed created /*AccountSeedListViewModel accountSeedListViewModel = ViewModelProviders.of(this).get(AccountSeedListViewModel.class); diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java new file mode 100644 index 0000000..a95210a --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java @@ -0,0 +1,74 @@ +package cy.agorise.crystalwallet.activities; + +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.Editable; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnTextChanged; +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.util.PasswordManager; +import cy.agorise.crystalwallet.viewmodels.AccountSeedViewModel; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; + +public class PinRequestActivity extends AppCompatActivity { + private String passwordEncrypted; + + @Override + public void onBackPressed() { + //Do nothing to prevent the user to use the back button + } + + @BindView(R.id.etPassword) + EditText etPassword; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_pin_request); + ButterKnife.bind(this); + + GeneralSettingListViewModel generalSettingListViewModel = ViewModelProviders.of(this).get(GeneralSettingListViewModel.class); + LiveData> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList(); + generalSettingsLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + passwordEncrypted = ""; + + if (generalSettings != null){ + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_PASSWORD)){ + if (!generalSetting.getValue().isEmpty()){ + passwordEncrypted = generalSetting.getValue(); + } + break; + } + } + } + } + }); + } + + @OnTextChanged(value = R.id.etPassword, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterPasswordChanged(Editable editable) { + if (PasswordManager.checkPassword(passwordEncrypted, etPassword.getText().toString())) { + this.finish(); + } + } +} + + diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java index ec99d7c..8aa4b56 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java @@ -37,8 +37,9 @@ public abstract class BitsharesFaucetApiGenerator { * @param url The url of the faucet * @return The bitshares id of the registered account, or null */ - public static boolean registerBitsharesAccount(String accountName, String ownerKey, - String activeKey, String memoKey, String url){ + public static void registerBitsharesAccount(String accountName, String ownerKey, + String activeKey, String memoKey, String url, + final ApiRequest request){ CreateAccountPetition petition = new CreateAccountPetition(); final Account account = new Account(); account.name=accountName; @@ -62,8 +63,7 @@ public abstract class BitsharesFaucetApiGenerator { HashMap hashMap = new HashMap<>(); hashMap.put("account", hm); - final boolean[] answer = {false}; - final Object SYNC = new Object(); + try { ServiceGenerator sg = new ServiceGenerator(url); IWebService service = sg.getService(IWebService.class); @@ -78,56 +78,36 @@ public abstract class BitsharesFaucetApiGenerator { if (resp.account != null) { try { if(resp.account.name.equals(account.name)) { - synchronized (SYNC){ - answer[0] = true; - SYNC.notifyAll(); - } + request.getListener().success(true,request.getId()); }else{ - System.out.println("ERROR account name different" + resp.account.name); - //ERROR - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } } catch (Exception e) { e.printStackTrace(); - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } }else{ System.out.println("ERROR response doesn't have account " + response.message()); - //ERROR - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } }else{ System.out.println("ERROR fetching info"); - //ERROR - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } } @Override public void onFailure(Call call, Throwable t) { t.printStackTrace(); - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } }); - synchronized (SYNC) { - SYNC.wait(60000); - } } catch (Exception e) { e.printStackTrace(); - } + request.getListener().fail(request.getId()); - return answer[0]; + } } /** diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java index 15ea05f..0522a9c 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java @@ -12,14 +12,14 @@ import cy.agorise.crystalwallet.dao.BitsharesAssetDao; import cy.agorise.crystalwallet.dao.CryptoCoinBalanceDao; import cy.agorise.crystalwallet.dao.CryptoCurrencyDao; import cy.agorise.crystalwallet.dao.CrystalDatabase; -import cy.agorise.crystalwallet.dao.TransactionDao; -import cy.agorise.crystalwallet.manager.BitsharesAccountManager; +import cy.agorise.crystalwallet.enums.CryptoNet; import cy.agorise.crystalwallet.models.BitsharesAsset; import cy.agorise.crystalwallet.models.BitsharesAssetInfo; import cy.agorise.crystalwallet.models.CryptoCoinBalance; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; import cy.agorise.crystalwallet.models.CryptoCurrency; import cy.agorise.crystalwallet.models.CryptoCurrencyEquivalence; +import cy.agorise.crystalwallet.network.CryptoNetManager; import cy.agorise.crystalwallet.network.WebSocketThread; import cy.agorise.graphenej.Address; import cy.agorise.graphenej.Asset; @@ -43,11 +43,9 @@ import cy.agorise.graphenej.api.TransactionBroadcastSequence; import cy.agorise.graphenej.interfaces.NodeErrorListener; import cy.agorise.graphenej.interfaces.SubscriptionListener; import cy.agorise.graphenej.interfaces.WitnessResponseListener; -import cy.agorise.graphenej.models.AccountBalanceUpdate; import cy.agorise.graphenej.models.AccountProperties; import cy.agorise.graphenej.models.BaseResponse; import cy.agorise.graphenej.models.BroadcastedTransaction; -import cy.agorise.graphenej.models.HistoricalTransfer; import cy.agorise.graphenej.models.SubscriptionResponse; import cy.agorise.graphenej.models.WitnessResponse; import cy.agorise.graphenej.operations.TransferOperation; @@ -62,12 +60,8 @@ public abstract class GrapheneApiGenerator { //TODO network connections //TODO make to work with all Graphene type, not only bitshares - public static String url = "http://185.208.208.147:11012"; public static String faucetUrl = "http://185.208.208.147:5010"; - private static String equivalentUrl = "http://185.208.208.147:8090"; - //public static String url = "wss://bitshares.openledger.info/ws"; - //private static Str ing equivalentUrl = "wss://bitshares.openledger.info/ws"; - + private static String equivalentUrl = "wss://bitshares.openledger.info/ws"; // The message broker for bitshares private static SubscriptionMessagesHub bitsharesSubscriptionHub = new SubscriptionMessagesHub("", "", true, new NodeErrorListener() { @@ -81,7 +75,7 @@ public abstract class GrapheneApiGenerator { /** * The subscription thread for the real time updates */ - private static WebSocketThread subscriptionThread = new WebSocketThread(bitsharesSubscriptionHub,url); + private static WebSocketThread subscriptionThread = new WebSocketThread(bitsharesSubscriptionHub, CryptoNetManager.getURL(CryptoNet.BITSHARES)); /** * This is used for manager each listener in the subscription thread */ @@ -114,7 +108,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -142,7 +136,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -169,7 +163,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -196,7 +190,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -223,7 +217,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -247,7 +241,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -292,7 +286,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -340,7 +334,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -353,12 +347,10 @@ public abstract class GrapheneApiGenerator { */ public static void subscribeBitsharesAccount(final long accountId, final String accountBitsharesId, final Context context){ - System.out.println("GrapheneAPI subscribe to account balance update"); if(!currentBitsharesListener.containsKey(accountId)){ CrystalDatabase db = CrystalDatabase.getAppDatabase(context); final BitsharesAssetDao bitsharesAssetDao = db.bitsharesAssetDao(); final CryptoCurrencyDao cryptoCurrencyDao = db.cryptoCurrencyDao(); - final TransactionDao transactionDao = db.transactionDao(); SubscriptionListener balanceListener = new SubscriptionListener() { @Override public ObjectType getInterestObjectType() { @@ -374,16 +366,15 @@ public abstract class GrapheneApiGenerator { BroadcastedTransaction transactionUpdate = (BroadcastedTransaction) update; for(BaseOperation operation : transactionUpdate.getTransaction().getOperations()){ if(operation instanceof TransferOperation){ - TransferOperation tOperation = (TransferOperation) operation; + final TransferOperation tOperation = (TransferOperation) operation; if(tOperation.getFrom().getObjectId().equals(accountBitsharesId) || tOperation.getTo().getObjectId().equals(accountBitsharesId)){ GrapheneApiGenerator.getAccountBalance(accountId,accountBitsharesId,context); - CryptoCoinTransaction transaction = new CryptoCoinTransaction(); + final CryptoCoinTransaction transaction = new CryptoCoinTransaction(); transaction.setAccountId(accountId); transaction.setAmount(tOperation.getAssetAmount().getAmount().longValue()); BitsharesAssetInfo info = bitsharesAssetDao.getBitsharesAssetInfoById(tOperation.getAssetAmount().getAsset().getObjectId()); if (info == null) { //The cryptoCurrency is not in the database, queringfor its data - final Object SYNC = new Object(); //Object to syn the answer ApiRequest assetRequest = new ApiRequest(0, new ApiRequestListener() { @Override public void success(Object answer, int idPetition) { @@ -394,40 +385,21 @@ public abstract class GrapheneApiGenerator { info.setCryptoCurrencyId(idCryptoCurrency); asset.setId((int)idCryptoCurrency); bitsharesAssetDao.insertBitsharesAssetInfo(info); - } - synchronized (SYNC){ - SYNC.notifyAll(); + saveTransaction(transaction,(int)info.getCryptoCurrencyId(),accountBitsharesId,tOperation,context); } } @Override public void fail(int idPetition) { - synchronized (SYNC){ - SYNC.notifyAll(); - } + //TODO error retrieving asset } }); ArrayList assets = new ArrayList<>(); assets.add(tOperation.getAssetAmount().getAsset().getObjectId()); GrapheneApiGenerator.getAssetById(assets,assetRequest); - - synchronized (SYNC){ - try {SYNC.wait(60000);} catch (InterruptedException ignore) {} - } - info = bitsharesAssetDao.getBitsharesAssetInfoById(tOperation.getAssetAmount().getAsset().getObjectId()); + }else{ + saveTransaction(transaction,(int)info.getCryptoCurrencyId(),accountBitsharesId,tOperation,context); } - if( info == null){ - //We couldn't retrieve the cryptocurrency - return; - } - transaction.setIdCurrency((int)info.getCryptoCurrencyId()); - transaction.setConfirmed(true); //graphene transaction are always confirmed - transaction.setFrom(tOperation.getFrom().getObjectId()); - transaction.setInput(!tOperation.getFrom().getObjectId().equals(accountBitsharesId)); - transaction.setTo(tOperation.getTo().getObjectId()); - transaction.setDate(new Date()); - transactionDao.insertTransaction(transaction); - //GrapheneApiGenerator.getBlockHeaderTime(, new ApiRequest(0, new BitsharesAccountManager.GetTransactionDate(transaction, db.transactionDao()))); } } } @@ -449,6 +421,26 @@ public abstract class GrapheneApiGenerator { } } + /** + * Fucniton to save a transaction retrieved from the update + * @param transaction The transaction db object + * @param currencyId The id of the currency on the database + * @param accountBitsharesId The id of the account in the bitshares network + * @param tOperation The transfer operation fetched from the update + * @param context The context of this app + */ + private static void saveTransaction(CryptoCoinTransaction transaction, int currencyId, + String accountBitsharesId, TransferOperation tOperation , + Context context){ + transaction.setIdCurrency(currencyId); + transaction.setConfirmed(true); //graphene transaction are always confirmed + transaction.setFrom(tOperation.getFrom().getObjectId()); + transaction.setInput(!tOperation.getFrom().getObjectId().equals(accountBitsharesId)); + transaction.setTo(tOperation.getTo().getObjectId()); + transaction.setDate(new Date()); + CrystalDatabase.getAppDatabase(context).transactionDao().insertTransaction(transaction); + } + /** * Cancels all bitshares account subscriptions */ @@ -515,7 +507,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); @@ -542,7 +534,7 @@ public abstract class GrapheneApiGenerator { public void onError(BaseResponse.Error error) { request.getListener().fail(request.getId()); } - }),url); + }),CryptoNetManager.getURL(CryptoNet.BITSHARES)); thread.start(); } @@ -674,7 +666,7 @@ public abstract class GrapheneApiGenerator { Converter converter = new Converter(); order.getSellPrice().base.getAsset().setPrecision(baseAsset.getPrecision()); order.getSellPrice().quote.getAsset().setPrecision(quoteAsset.getPrecision()); - double equiValue = converter.getConversionRate(order.getSellPrice(), Converter.BASE_TO_QUOTE); + double equiValue = converter.getConversionRate(order.getSellPrice(), Converter.QUOTE_TO_BASE); CryptoCurrencyEquivalence equivalence = new CryptoCurrencyEquivalence(baseAsset.getId(), quoteAsset.getId(), (int) (Math.pow(10, baseAsset.getPrecision()) * equiValue), new Date()); CrystalDatabase.getAppDatabase(context).cryptoCurrencyEquivalenceDao().insertCryptoCurrencyEquivalence(equivalence); break; diff --git a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java index fc5f6c7..8a1e16c 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java +++ b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java @@ -6,6 +6,11 @@ import android.content.Intent; import com.idescout.sql.SqlScoutServer; import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.enums.CryptoNet; +import cy.agorise.crystalwallet.models.BitsharesAsset; +import cy.agorise.crystalwallet.models.BitsharesAssetInfo; +import cy.agorise.crystalwallet.models.CryptoCurrencyEquivalence; +import cy.agorise.crystalwallet.network.CryptoNetManager; import cy.agorise.crystalwallet.service.CrystalWalletService; /** @@ -15,6 +20,25 @@ import cy.agorise.crystalwallet.service.CrystalWalletService; */ public class CrystalApplication extends Application { + public static String BITSHARES_URL[] = + { + "wss://de.palmpay.io/ws", // Custom node + "wss://bitshares.nu/ws", + "wss://dexnode.net/ws", // Dallas, USA + "wss://bitshares.crypto.fans/ws", // Munich, Germany + "wss://bitshares.openledger.info/ws", // Openledger node + "ws://185.208.208.147:8090" // Custom node + }; + + public static String BITSHARES_TESTNET_URL[] = + { + "http://185.208.208.147:11012", // Openledger node + }; + + //This is for testing the equivalent values on the testnet TODO remove + public static BitsharesAsset bitUSDAsset = new BitsharesAsset("USD",4,"1.3.121",BitsharesAsset.Type.SMART_COIN); + //This is for testing the equivalent values on the testnet TODO remove + public static BitsharesAsset bitEURAsset = new BitsharesAsset("EUR",4,"1.3.120",BitsharesAsset.Type.SMART_COIN); @Override public void onCreate() { @@ -24,6 +48,36 @@ public class CrystalApplication extends Application { CrystalDatabase db = CrystalDatabase.getAppDatabase(this.getApplicationContext()); SqlScoutServer.create(this, getPackageName()); + //Using Bitshares Agorise Testnet + CryptoNetManager.addCryptoNetURL(CryptoNet.BITSHARES,BITSHARES_TESTNET_URL); + + //This is for testing the equivalent values on the testnet TODO remove + if(db.bitsharesAssetDao().getBitsharesAssetInfoById(bitEURAsset.getBitsharesId())== null){ + if(db.cryptoCurrencyDao().getByName(bitEURAsset.getName())== null){ + db.cryptoCurrencyDao().insertCryptoCurrency(bitEURAsset); + } + long idCurrency = db.cryptoCurrencyDao().getByName(bitEURAsset.getName()).getId(); + BitsharesAssetInfo info = new BitsharesAssetInfo(bitEURAsset); + info.setCryptoCurrencyId(idCurrency); + db.bitsharesAssetDao().insertBitsharesAssetInfo(info); + + } + + //This is for testing the equivalent values on the testnet TODO remove + if(db.bitsharesAssetDao().getBitsharesAssetInfoById(bitUSDAsset.getBitsharesId())== null){ + if(db.cryptoCurrencyDao().getByName(bitUSDAsset.getName())== null){ + db.cryptoCurrencyDao().insertCryptoCurrency(bitUSDAsset); + } + long idCurrency = db.cryptoCurrencyDao().getByName(bitUSDAsset.getName()).getId(); + BitsharesAssetInfo info = new BitsharesAssetInfo(bitUSDAsset); + info.setCryptoCurrencyId(idCurrency); + db.bitsharesAssetDao().insertBitsharesAssetInfo(info); + + } + + //Next line is for use the bitshares main net + //CryptoNetManager.addCryptoNetURL(CryptoNet.BITSHARES,BITSHARES_URL); + Intent intent = new Intent(getApplicationContext(), CrystalWalletService.class); startService(intent); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java new file mode 100644 index 0000000..852daa9 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java @@ -0,0 +1,109 @@ +package cy.agorise.crystalwallet.application; + +import android.app.Activity; + +import android.app.Application; +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.annotation.Nullable; +import android.support.v4.app.FragmentActivity; + +import java.util.List; + +import cy.agorise.crystalwallet.activities.PinRequestActivity; +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; + +/** + * Created by Henry Varona on 27/1/2018. + */ + +public class CrystalSecurityMonitor implements Application.ActivityLifecycleCallbacks { + private int numStarted = 0; + private String passwordEncrypted; + + public CrystalSecurityMonitor(final FragmentActivity fragmentActivity){ + GeneralSettingListViewModel generalSettingListViewModel = ViewModelProviders.of(fragmentActivity).get(GeneralSettingListViewModel.class); + LiveData> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList(); + + generalSettingsLiveData.observe(fragmentActivity, new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + boolean founded = false; + passwordEncrypted = ""; + + if (generalSettings != null){ + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_PASSWORD)){ + founded = true; + if (!generalSetting.getValue().isEmpty()){ + passwordEncrypted = generalSetting.getValue(); + callPasswordRequest(fragmentActivity); + } + break; + } + } + } + } + }); + } + + @Override + public void onActivityStarted(Activity activity) { + if (numStarted == 0) { + if ((this.passwordEncrypted != null) && (!this.passwordEncrypted.equals(""))) { + callPasswordRequest(activity); + } + } + numStarted++; + } + + @Override + public void onActivityStopped(Activity activity) { + numStarted--; + if (numStarted == 0) { + if ((this.passwordEncrypted != null) && (!this.passwordEncrypted.equals(""))) { + callPasswordRequest(activity); + } + } + } + + public void callPasswordRequest(Activity activity){ + if ((!activity.getIntent().hasExtra("ACTIVITY_TYPE")) || (!activity.getIntent().getStringExtra("ACTIVITY_TYPE").equals("PASSWORD_REQUEST"))) { + Intent intent = new Intent(activity, PinRequestActivity.class); + intent.putExtra("ACTIVITY_TYPE", "PASSWORD_REQUEST"); + activity.startActivity(intent); + } + } + + @Override + public void onActivityCreated(Activity activity, Bundle bundle) { + // + } + + @Override + public void onActivityResumed(Activity activity) { + // + } + + @Override + public void onActivityPaused(Activity activity) { + // + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { + // + } + + @Override + public void onActivityDestroyed(Activity activity) { + // + } + + + +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java index 77dd3fd..97d1e51 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java @@ -3,13 +3,16 @@ package cy.agorise.crystalwallet.dao; import android.arch.lifecycle.LiveData; import android.arch.paging.LivePagedListProvider; import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Delete; import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; import android.arch.persistence.room.Query; +import android.arch.persistence.room.Update; import java.util.List; import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.ContactAddress; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; /** @@ -24,6 +27,33 @@ public interface ContactDao { @Query("SELECT * FROM contact ORDER BY name ASC") LivePagedListProvider contactsByName(); + @Query("SELECT c.* FROM contact c WHERE c.id IN (SELECT DISTINCT(ca.contact_id) FROM contact_address ca WHERE ca.crypto_net == :cryptoNet) ORDER BY name ASC, email ASC") + LivePagedListProvider contactsByNameAndCryptoNet(String cryptoNet); + @Query("SELECT * FROM contact WHERE id = :id") LiveData getById(long id); + + @Query("SELECT count(*) FROM contact WHERE name = :name") + boolean existsByName(String name); + + @Query("SELECT * FROM contact_address WHERE contact_id = :contactId") + LiveData> getContactAddresses(long contactId); + + @Update(onConflict = OnConflictStrategy.ABORT) + public void update(Contact... contacts); + + @Insert(onConflict = OnConflictStrategy.ABORT) + public long[] add(Contact... contacts); + + @Insert(onConflict = OnConflictStrategy.ABORT) + public void addAddresses(ContactAddress... contactAddresses); + + @Update(onConflict = OnConflictStrategy.REPLACE) + public void updateAddresses(ContactAddress... contactAddresses); + + @Update(onConflict = OnConflictStrategy.REPLACE) + public void updateAddressesFields(ContactAddress... contactAddresses); + + @Delete + public void deleteContacts(Contact... contacts); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java index 6826c4f..f821a35 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java @@ -1,5 +1,6 @@ package cy.agorise.crystalwallet.dao; +import android.arch.lifecycle.LiveData; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; @@ -28,6 +29,9 @@ public interface CryptoCurrencyDao { @Query("SELECT * FROM crypto_currency WHERE id IN (:ids)") List getByIds(List ids); + @Query("SELECT * FROM crypto_currency WHERE name = :name") + LiveData getLiveDataByName(String name); + @Query("SELECT * FROM crypto_currency WHERE name = :name") CryptoCurrency getByName(String name); diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java b/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java index dc8b951..d0c79ba 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java @@ -10,6 +10,7 @@ import cy.agorise.crystalwallet.dao.converters.Converters; import cy.agorise.crystalwallet.models.AccountSeed; import cy.agorise.crystalwallet.models.BitsharesAssetInfo; import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.ContactAddress; import cy.agorise.crystalwallet.models.CryptoCoinBalance; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; import cy.agorise.crystalwallet.models.CryptoCurrency; @@ -28,6 +29,7 @@ import cy.agorise.crystalwallet.models.GrapheneAccountInfo; CryptoNetAccount.class, CryptoCoinTransaction.class, Contact.class, + ContactAddress.class, CryptoCurrency.class, CryptoCoinBalance.class, GrapheneAccountInfo.class, diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java index 5b6673a..151b9f9 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java @@ -7,6 +7,7 @@ import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; import android.arch.persistence.room.Query; +import java.util.Date; import java.util.List; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; @@ -32,6 +33,9 @@ public interface TransactionDao { @Query("SELECT * FROM crypto_coin_transaction WHERE id = :id") LiveData getById(long id); + @Query("SELECT * FROM crypto_coin_transaction WHERE date = :date and 'from' = :from and 'to' = :to and amount = :amount ") + CryptoCoinTransaction getByTransaction(Date date, String from, String to, long amount); + @Insert(onConflict = OnConflictStrategy.REPLACE) public long[] insertTransaction(CryptoCoinTransaction... transactions); diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactSelectionFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactSelectionFragment.java new file mode 100644 index 0000000..46576d5 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactSelectionFragment.java @@ -0,0 +1,129 @@ +package cy.agorise.crystalwallet.fragments; + +import android.app.Dialog; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.arch.paging.PagedList; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; + +import com.google.zxing.Result; + +import java.util.ArrayList; +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import butterknife.OnItemSelected; +import butterknife.OnTextChanged; +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.cryptonetinforequests.CryptoNetInfoRequestListener; +import cy.agorise.crystalwallet.cryptonetinforequests.CryptoNetInfoRequests; +import cy.agorise.crystalwallet.cryptonetinforequests.ValidateBitsharesSendRequest; +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.enums.CryptoNet; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.CryptoCoinBalance; +import cy.agorise.crystalwallet.models.CryptoCurrency; +import cy.agorise.crystalwallet.models.CryptoNetAccount; +import cy.agorise.crystalwallet.models.GrapheneAccount; +import cy.agorise.crystalwallet.viewmodels.ContactListViewModel; +import cy.agorise.crystalwallet.viewmodels.CryptoNetAccountListViewModel; +import cy.agorise.crystalwallet.viewmodels.validators.SendTransactionValidator; +import cy.agorise.crystalwallet.viewmodels.validators.UIValidatorListener; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.ValidationField; +import cy.agorise.crystalwallet.views.ContactSelectionListAdapter; +import cy.agorise.crystalwallet.views.CryptoCurrencyAdapter; +import cy.agorise.crystalwallet.views.CryptoNetAccountAdapter; +import me.dm7.barcodescanner.zxing.ZXingScannerView; + +public class ContactSelectionFragment extends DialogFragment implements ContactSelectionListAdapter.ContactSelectionListAdapterListener{ + + private CryptoNet cryptoNet; + private CrystalDatabase db; + private AlertDialog.Builder builder; + + @BindView(R.id.contactListView) + RecyclerView contactSelectionListView; + + public static ContactSelectionFragment newInstance(CryptoNet cryptoNet) { + ContactSelectionFragment f = new ContactSelectionFragment(); + + // Supply num input as an argument. + Bundle args = new Bundle(); + args.putString("CRYPTO_NET", cryptoNet.name()); + f.setArguments(args); + + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + //AlertDialog.Builder + builder = new AlertDialog.Builder(getActivity(), R.style.SendTransactionTheme); + + LayoutInflater inflater = getActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.contact_list, null); + ButterKnife.bind(this, view); + + final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this.getContext()); + this.contactSelectionListView.setLayoutManager(linearLayoutManager); + //Prevents the list to start again when scrolling to the end + this.contactSelectionListView.setNestedScrollingEnabled(false); + final ContactSelectionListAdapter contactSelectionListAdapter = new ContactSelectionListAdapter(); + contactSelectionListAdapter.setListener(this); + contactSelectionListView.setAdapter(contactSelectionListAdapter); + + this.cryptoNet = CryptoNet.valueOf(getArguments().getString("CRYPTO_NET")); + if (this.cryptoNet != null) { + ContactListViewModel contactListViewModel = ViewModelProviders.of(this).get(ContactListViewModel.class); + contactListViewModel.init(this.cryptoNet); + LiveData> contactsLiveData = contactListViewModel.getContactList(); + + contactsLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable PagedList contacts) { + contactSelectionListAdapter.setList(contacts); + } + }); + } + + return builder.setView(view).create(); + } + + @Override + public void onContactSelected(Contact contact) { + Intent result = new Intent(); + result.putExtra("CONTACT_ID", contact.getId()); + getTargetFragment().onActivityResult(getTargetRequestCode(), 1, result); + this.dismiss(); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactsFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactsFragment.java index 558d81b..6f34284 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactsFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactsFragment.java @@ -1,14 +1,28 @@ package cy.agorise.crystalwallet.fragments; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.arch.paging.PagedList; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import butterknife.BindView; +import butterknife.ButterKnife; import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.viewmodels.ContactListViewModel; +import cy.agorise.crystalwallet.views.ContactListView; public class ContactsFragment extends Fragment { + + @BindView(R.id.vContactListView) + ContactListView contactListView; + public ContactsFragment() { // Required empty public constructor } @@ -29,6 +43,20 @@ public class ContactsFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment - return inflater.inflate(R.layout.fragment_contacts, container, false); + View v = inflater.inflate(R.layout.fragment_contacts, container, false); + ButterKnife.bind(this, v); + + ContactListViewModel contactListViewModel = ViewModelProviders.of(this).get(ContactListViewModel.class); + contactListViewModel.init(); + LiveData> contactsLiveData = contactListViewModel.getContactList(); + + contactsLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable PagedList contacts) { + contactListView.setData(contacts); + } + }); + + return v; } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralSettingsFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralSettingsFragment.java index 7be6e28..e91e848 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralSettingsFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralSettingsFragment.java @@ -1,19 +1,44 @@ package cy.agorise.crystalwallet.fragments; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Currency; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +import butterknife.BindView; import butterknife.ButterKnife; +import butterknife.OnItemSelected; import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; /** * Created by xd on 12/28/17. */ public class GeneralSettingsFragment extends Fragment { + + private HashMap countriesMap; + private GeneralSettingListViewModel generalSettingListViewModel; + private LiveData> generalSettingListLiveData; + + @BindView (R.id.spTaxableCountry) + Spinner spTaxableCountry; + public GeneralSettingsFragment() { // Required empty public constructor } @@ -37,6 +62,83 @@ public class GeneralSettingsFragment extends Fragment { View v = inflater.inflate(R.layout.fragment_general_settings, container, false); ButterKnife.bind(this, v); + generalSettingListViewModel = ViewModelProviders.of(this).get(GeneralSettingListViewModel.class); + generalSettingListLiveData = generalSettingListViewModel.getGeneralSettingList(); + + // Initializes the countries spinner + countriesMap = new HashMap(); + String[] countryCodeList = Locale.getISOCountries(); + ArrayList countryAndCurrencyList = new ArrayList(); + String countryAndCurrencyLabel = ""; + for (String countryCode : countryCodeList) { + Locale locale = new Locale("", countryCode); + try { + Currency currency = Currency.getInstance(locale); + countryAndCurrencyLabel = locale.getDisplayCountry() + " (" + currency.getCurrencyCode() + ")"; + countryAndCurrencyList.add(countryAndCurrencyLabel); + countriesMap.put(countryCode, countryAndCurrencyLabel); + countriesMap.put(countryAndCurrencyLabel, countryCode); + } catch (Exception e) { + + } + } + Collections.sort(countryAndCurrencyList); + countryAndCurrencyList.add(0,"SELECT COUNTRY"); + ArrayAdapter countryAdapter = new ArrayAdapter(this.getContext(), android.R.layout.simple_spinner_item, countryAndCurrencyList); + spTaxableCountry.setAdapter(countryAdapter); + + //Observes the general settings data + generalSettingListLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + loadSettings(generalSettings); + } + }); + return v; } + + public GeneralSetting getSetting(String name){ + for (GeneralSetting generalSetting:this.generalSettingListLiveData.getValue()) { + if (generalSetting.getName().equals(name)) { + return generalSetting; + } + } + + return null; + } + + @OnItemSelected(R.id.spTaxableCountry) + void onItemSelected(int position) { + if (position != 0) { + GeneralSetting generalSettingCountryCode = this.getSetting(GeneralSetting.SETTING_NAME_PREFERED_COUNTRY); + GeneralSetting generalSettingCurrency = this.getSetting(GeneralSetting.SETTING_NAME_PREFERED_CURRENCY); + + if (generalSettingCountryCode == null){ + generalSettingCountryCode = new GeneralSetting(); + generalSettingCountryCode.setName(GeneralSetting.SETTING_NAME_PREFERED_COUNTRY); + } + if (generalSettingCurrency == null){ + generalSettingCurrency = new GeneralSetting(); + generalSettingCurrency.setName(GeneralSetting.SETTING_NAME_PREFERED_CURRENCY); + } + + String countryCode = countriesMap.get((String) spTaxableCountry.getSelectedItem()); + Locale locale = new Locale("", countryCode); + Currency currency = Currency.getInstance(locale); + + generalSettingCountryCode.setValue(countryCode); + generalSettingCurrency.setValue(currency.getCurrencyCode()); + this.generalSettingListViewModel.saveGeneralSettings(generalSettingCountryCode, generalSettingCurrency); + } + } + + public void loadSettings(List generalSettings){ + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_NAME_PREFERED_COUNTRY)){ + String preferedCountryCode = generalSetting.getValue(); + spTaxableCountry.setSelection(((ArrayAdapter)spTaxableCountry.getAdapter()).getPosition(countriesMap.get(preferedCountryCode))); + } + } + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/PinSecurityFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/PinSecurityFragment.java index 52e2c55..6c75211 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/PinSecurityFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/PinSecurityFragment.java @@ -1,19 +1,60 @@ package cy.agorise.crystalwallet.fragments; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.text.Editable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; +import java.util.List; + +import butterknife.BindView; import butterknife.ButterKnife; +import butterknife.OnTextChanged; import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.activities.CreateSeedActivity; +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.util.PasswordManager; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; +import cy.agorise.crystalwallet.viewmodels.validators.PinSecurityValidator; +import cy.agorise.crystalwallet.viewmodels.validators.UIValidatorListener; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.ValidationField; /** * Created by xd on 1/18/18. */ -public class PinSecurityFragment extends Fragment { +public class PinSecurityFragment extends Fragment implements UIValidatorListener { + + @BindView(R.id.tvCurrentPin) + TextView tvCurrentPin; + @BindView(R.id.etCurrentPin) + EditText etCurrentPin; + @BindView(R.id.etNewPin) + EditText etNewPin; + @BindView(R.id.etConfirmPin) + EditText etConfirmPin; + + @BindView(R.id.tvCurrentPinError) + TextView tvCurrentPinError; + @BindView(R.id.tvNewPinError) + TextView tvNewPinError; + @BindView(R.id.tvConfirmPinError) + TextView tvConfirmPinError; + + GeneralSettingListViewModel generalSettingListViewModel; + GeneralSetting passwordGeneralSetting; + PinSecurityValidator pinSecurityValidator; public PinSecurityFragment() { // Required empty public constructor @@ -33,6 +74,140 @@ public class PinSecurityFragment extends Fragment { View v = inflater.inflate(R.layout.fragment_pin_security, container, false); ButterKnife.bind(this, v); + generalSettingListViewModel = ViewModelProviders.of(this).get(GeneralSettingListViewModel.class); + LiveData> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList(); + + pinSecurityValidator = new PinSecurityValidator(this.getContext(), etCurrentPin, etNewPin, etConfirmPin); + pinSecurityValidator.setListener(this); + + generalSettingsLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + boolean founded = false; + + if (generalSettings != null){ + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_PASSWORD)){ + founded = true; + if (!generalSetting.getValue().isEmpty()){ + passwordGeneralSetting = generalSetting; + showCurrentPinUI(true); + } else { + showCurrentPinUI(false); + } + break; + } + } + if (!founded){ + showCurrentPinUI(false); + } + } else { + showCurrentPinUI(false); + } + } + }); + return v; } + + public void showCurrentPinUI(Boolean visible){ + if (visible){ + tvCurrentPin.setVisibility(View.VISIBLE); + etCurrentPin.setVisibility(View.VISIBLE); + } else { + tvCurrentPin.setVisibility(View.GONE); + etCurrentPin.setVisibility(View.GONE); + } + } + + @OnTextChanged(value = R.id.etCurrentPin, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterCurrentPinChanged(Editable editable) { + this.pinSecurityValidator.validate(); + } + + @OnTextChanged(value = R.id.etNewPin, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterNewPinChanged(Editable editable) { + this.pinSecurityValidator.validate(); + } + + @OnTextChanged(value = R.id.etConfirmPin, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterConfirmPinChanged(Editable editable) { + this.pinSecurityValidator.validate(); + } + + public void clearFields(){ + if (!this.etCurrentPin.getText().toString().equals("")) { + this.etCurrentPin.setText(""); + } + if (!this.etNewPin.getText().toString().equals("")) { + this.etNewPin.setText(""); + } + if (!this.etConfirmPin.getText().toString().equals("")) { + this.etConfirmPin.setText(""); + } + } + + @Override + public void onValidationSucceeded(final ValidationField field) { + final PinSecurityFragment fragment = this; + + this.getActivity().runOnUiThread(new Runnable() { + public void run() { + + if (field.getView() == etCurrentPin) { + tvCurrentPinError.setText(""); + } else if (field.getView() == etNewPin){ + tvNewPinError.setText(""); + } else if (field.getView() == etConfirmPin){ + tvConfirmPinError.setText(""); + } + + if (pinSecurityValidator.isValid()){ + CharSequence text = "Your password has been sucessfully changed!"; + int duration = Toast.LENGTH_SHORT; + + Toast toast = Toast.makeText(getContext(), text, duration); + toast.show(); + //showCurrentPinUI(true); + + savePassword(etNewPin.getText().toString()); + + + clearFields(); + } + } + }); + } + + public void savePassword(String password) { + String passwordEncripted = PasswordManager.encriptPassword(password); + + if (passwordGeneralSetting == null) { + passwordGeneralSetting = new GeneralSetting(); + passwordGeneralSetting.setName(GeneralSetting.SETTING_PASSWORD); + } + + passwordGeneralSetting.setValue(passwordEncripted); + generalSettingListViewModel.saveGeneralSetting(passwordGeneralSetting); + } + + @Override + public void onValidationFailed(final ValidationField field) { + this.getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + if (field.getView() == etCurrentPin) { + tvCurrentPinError.setText(field.getMessage()); + } else if (field.getView() == etNewPin){ + tvNewPinError.setText(field.getMessage()); + } else if (field.getView() == etConfirmPin){ + tvConfirmPinError.setText(field.getMessage()); + } + } + }); + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/ReceiveTransactionFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/ReceiveTransactionFragment.java index 3e26231..db94f95 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/ReceiveTransactionFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/ReceiveTransactionFragment.java @@ -4,6 +4,7 @@ import android.app.Dialog; import android.app.LauncherActivity; import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.content.DialogInterface; import android.graphics.Bitmap; import android.os.Bundle; @@ -31,6 +32,8 @@ import com.google.zxing.WriterException; import com.google.zxing.common.BitMatrix; import butterknife.OnClick; +import cy.agorise.crystalwallet.viewmodels.CryptoNetAccountListViewModel; +import cy.agorise.crystalwallet.views.CryptoNetAccountAdapter; import cy.agorise.graphenej.Invoice; import java.util.ArrayList; import java.util.List; @@ -57,6 +60,8 @@ public class ReceiveTransactionFragment extends DialogFragment implements UIVali ReceiveTransactionValidator receiveTransactionValidator; + @BindView(R.id.spTo) + Spinner spTo; @BindView(R.id.etAmount) EditText etAmount; @BindView(R.id.tvAmountError) @@ -145,6 +150,12 @@ public class ReceiveTransactionFragment extends DialogFragment implements UIVali receiveTransactionValidator = new ReceiveTransactionValidator(this.getContext(), this.cryptoNetAccount, spAsset, etAmount); receiveTransactionValidator.setListener(this); + + CryptoNetAccountListViewModel cryptoNetAccountListViewModel = ViewModelProviders.of(this).get(CryptoNetAccountListViewModel.class); + List cryptoNetAccounts = cryptoNetAccountListViewModel.getCryptoNetAccountList(); + CryptoNetAccountAdapter toSpinnerAdapter = new CryptoNetAccountAdapter(this.getContext(), android.R.layout.simple_spinner_item, cryptoNetAccounts); + spTo.setAdapter(toSpinnerAdapter); + spTo.setSelection(0); } builder.setView(view); @@ -199,6 +210,11 @@ public class ReceiveTransactionFragment extends DialogFragment implements UIVali }, 400); } + @OnItemSelected(R.id.spTo) + public void afterToSelected(Spinner spinner, int position) { + this.receiveTransactionValidator.validate(); + } + @OnTextChanged(value = R.id.etAmount, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) void afterAmountChanged(Editable editable) { diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/SendTransactionFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/SendTransactionFragment.java index 619d688..c73b340 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/SendTransactionFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/SendTransactionFragment.java @@ -5,6 +5,7 @@ import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; @@ -12,6 +13,7 @@ import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.text.Editable; @@ -23,13 +25,19 @@ import android.view.Window; import android.view.animation.LinearInterpolator; import android.widget.Button; import android.widget.EditText; +import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; +import android.widget.Toast; import com.google.zxing.Result; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import butterknife.BindView; import butterknife.ButterKnife; @@ -41,10 +49,13 @@ import cy.agorise.crystalwallet.cryptonetinforequests.CryptoNetInfoRequestListen import cy.agorise.crystalwallet.cryptonetinforequests.CryptoNetInfoRequests; import cy.agorise.crystalwallet.cryptonetinforequests.ValidateBitsharesSendRequest; import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.ContactAddress; import cy.agorise.crystalwallet.models.CryptoCoinBalance; import cy.agorise.crystalwallet.models.CryptoCurrency; import cy.agorise.crystalwallet.models.CryptoNetAccount; import cy.agorise.crystalwallet.models.GrapheneAccount; +import cy.agorise.crystalwallet.viewmodels.ContactViewModel; import cy.agorise.crystalwallet.viewmodels.CryptoNetAccountListViewModel; import cy.agorise.crystalwallet.viewmodels.CryptoNetAccountViewModel; import cy.agorise.crystalwallet.viewmodels.validators.SendTransactionValidator; @@ -52,6 +63,8 @@ import cy.agorise.crystalwallet.viewmodels.validators.UIValidatorListener; import cy.agorise.crystalwallet.viewmodels.validators.validationfields.ValidationField; import cy.agorise.crystalwallet.views.CryptoCurrencyAdapter; import cy.agorise.crystalwallet.views.CryptoNetAccountAdapter; +import cy.agorise.graphenej.Invoice; +import cy.agorise.graphenej.LineItem; import me.dm7.barcodescanner.zxing.ZXingScannerView; public class SendTransactionFragment extends DialogFragment implements UIValidatorListener, ZXingScannerView.ResultHandler { @@ -82,6 +95,9 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat FloatingActionButton btnSend; @BindView(R.id.btnCancel) TextView btnCancel; + @BindView(R.id.ivPeople) + ImageView ivPeople; + CryptoCurrencyAdapter assetAdapter; Button btnScanQrCode; @@ -145,7 +161,7 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat } List cryptoCurrencyList = db.cryptoCurrencyDao().getByIds(assetIds); - CryptoCurrencyAdapter assetAdapter = new CryptoCurrencyAdapter(getContext(), android.R.layout.simple_spinner_item, cryptoCurrencyList); + assetAdapter = new CryptoCurrencyAdapter(getContext(), android.R.layout.simple_spinner_item, cryptoCurrencyList); spAsset.setAdapter(assetAdapter); } }); @@ -222,6 +238,50 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat this.sendTransactionValidator.validate(); } + @OnClick(R.id.ivPeople) + public void searchContact(){ + FragmentTransaction ft = this.getActivity().getSupportFragmentManager().beginTransaction(); + Fragment prev = this.getActivity().getSupportFragmentManager().findFragmentByTag("ContactSelectionDialog"); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + + // Show a contact selection list + ContactSelectionFragment contactSelectionFragment = ContactSelectionFragment.newInstance(this.cryptoNetAccount.getCryptoNet()); + contactSelectionFragment.setTargetFragment(this, 1); + contactSelectionFragment.show(ft, "ContactSelectionDialog"); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode,resultCode,data); + + if(requestCode == 1) { + if(resultCode == 1) { + long contactId = data.getLongExtra("CONTACT_ID",-1); + if (contactId > -1){ + ContactViewModel contactViewModel = ViewModelProviders.of(this).get(ContactViewModel.class); + contactViewModel.init(contactId); + LiveData> contactAddressesLiveData = contactViewModel.getContactAddresses(); + + contactAddressesLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List contactAddresses) { + if (contactAddresses != null) { + for (ContactAddress contactAddress : contactAddresses) { + if (contactAddress.getCryptoNet() == cryptoNetAccount.getCryptoNet()) { + etTo.setText(contactAddress.getAddress()); + } + } + } + } + }); + } + } + } + } + @OnClick(R.id.btnCancel) public void cancel(){ this.dismiss(); @@ -317,6 +377,27 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat @Override public void handleResult(Result result) { + Invoice invoice = Invoice.fromQrCode(result.getText()); + + etTo.setText(invoice.getTo()); + + for (int i=0;i operationList = new ArrayList<>(); - operationList.add(builder.build()); + } - ECKey privateKey = sendRequest.getSourceAccount().getActiveKey(sendRequest.getContext()); + private void validateSendRequest(final ValidateBitsharesSendRequest sendRequest , final String idAsset){ + final Asset feeAsset = new Asset(idAsset); + final UserAccount fromUserAccount =new UserAccount(sendRequest.getSourceAccount().getAccountId()); - Transaction transaction = new Transaction(privateKey, null, operationList); - transaction.setChainId(BITSHARES_TESTNET_CHAIN_ID); + //TODO cached to accounts + this.getAccountInfoByName(sendRequest.getToAccount(), new ManagerRequest() { - ApiRequest transactionRequest = new ApiRequest(0, new ApiRequestListener() { @Override - public void success(Object answer, int idPetition) { - sendRequest.setSend(true); + public void success(Object answer) { + GrapheneAccount toUserGrapheneAccount = (GrapheneAccount) answer; + UserAccount toUserAccount = new UserAccount(toUserGrapheneAccount.getAccountId()); + TransferOperationBuilder builder = new TransferOperationBuilder() + .setSource(fromUserAccount) + .setDestination(toUserAccount) + .setTransferAmount(new AssetAmount(UnsignedLong.valueOf(sendRequest.getAmount()), new Asset(idAsset))) + .setFee(new AssetAmount(UnsignedLong.valueOf(0), feeAsset)); + if(sendRequest.getMemo() != null) { + //builder.setMemo(new Memo(fromUserAccount,toUserAccount,0,sendRequest.getMemo().getBytes())); + //TODO memo + System.out.println("transaction has memo"); + } + ArrayList operationList = new ArrayList<>(); + operationList.add(builder.build()); + + ECKey privateKey = sendRequest.getSourceAccount().getActiveKey(sendRequest.getContext()); + + Transaction transaction = new Transaction(privateKey, null, operationList); + transaction.setChainId(CryptoNetManager.getChaindId(CryptoNet.BITSHARES)); + + ApiRequest transactionRequest = new ApiRequest(0, new ApiRequestListener() { + @Override + public void success(Object answer, int idPetition) { + sendRequest.setSend(true); + } + + @Override + public void fail(int idPetition) { + sendRequest.setSend(false); + } + }); + + GrapheneApiGenerator.broadcastTransaction(transaction,feeAsset, transactionRequest); } @Override - public void fail(int idPetition) { - sendRequest.setSend(false); + public void fail() { + //TODO bad user to user account } }); - GrapheneApiGenerator.broadcastTransaction(transaction,feeAsset, transactionRequest); } /** * Returns the account info from a graphene id * @param grapheneId The graphene id of the account */ - private GrapheneAccount getAccountInfoById(String grapheneId){ - final Object SYNC = new Object(); - long timeout = 60000; + private void getAccountInfoById(String grapheneId, ManagerRequest request){ - AccountIdOrNameListener listener = new AccountIdOrNameListener(SYNC); + AccountIdOrNameListener listener = new AccountIdOrNameListener(request); - ApiRequest request = new ApiRequest(0, listener); - GrapheneApiGenerator.getAccountById(grapheneId,request); - - long cTime = System.currentTimeMillis(); - - while(!listener.ready && (System.currentTimeMillis()-cTime) < timeout){ - synchronized (SYNC){ - try { - SYNC.wait(100); - } catch (InterruptedException ignore) {} - } - } - - return listener.account; + ApiRequest accountRequest = new ApiRequest(0, listener); + GrapheneApiGenerator.getAccountById(grapheneId,accountRequest); } /** * Gets account info by its name * @param grapheneName The name of the account to retrieve */ - private GrapheneAccount getAccountInfoByName(String grapheneName){ - final Object SYNC = new Object(); - long timeout = 60000; + private void getAccountInfoByName(String grapheneName, ManagerRequest request){ - AccountIdOrNameListener listener = new AccountIdOrNameListener(SYNC); + AccountIdOrNameListener listener = new AccountIdOrNameListener(request); - ApiRequest request = new ApiRequest(0, listener); - GrapheneApiGenerator.getAccountByName(grapheneName,request); + ApiRequest accountRequest = new ApiRequest(0, listener); + GrapheneApiGenerator.getAccountByName(grapheneName,accountRequest); - long cTime = System.currentTimeMillis(); - - while(!listener.ready && (System.currentTimeMillis()-cTime) < timeout){ - synchronized (SYNC){ - try { - SYNC.wait(100); - } catch (InterruptedException ignore) {} - } - } - - return listener.account; } //TODO expand function to be more generic - private String getAssetInfoByName(String assetName){ - final Object SYNC = new Object(); - long timeout = 60000; + private void getAssetInfoByName(String assetName, ManagerRequest request){ - AssetIdOrNameListener nameListener = new AssetIdOrNameListener(SYNC); - ApiRequest request = new ApiRequest(0, nameListener); + AssetIdOrNameListener nameListener = new AssetIdOrNameListener(request); + ApiRequest assetRequest = new ApiRequest(0, nameListener); ArrayList assetNames = new ArrayList<>(); assetNames.add(assetName); - GrapheneApiGenerator.getAssetByName(assetNames, request); - - long cTime = System.currentTimeMillis(); - - while(!nameListener.ready && (System.currentTimeMillis()-cTime) < timeout){ - synchronized (SYNC){ - try { - SYNC.wait(100); - } catch (InterruptedException ignore) {} - } - } - - return nameListener.asset.getBitsharesId(); + GrapheneApiGenerator.getAssetByName(assetNames, assetRequest); } @@ -393,7 +462,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI * @param idAccount database id of the account * @param context The android context of this application */ - public static void refreshAccountTransactions(long idAccount, Context context){ + private static void refreshAccountTransactions(long idAccount, Context context){ CrystalDatabase db = CrystalDatabase.getAppDatabase(context); List transactions = db.transactionDao().getByIdAccount(idAccount); CryptoNetAccount account = db.cryptoNetAccountDao().getById(idAccount); @@ -449,23 +518,22 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI } /** - * Handles the succes request of the transaction, if the amount of transaction is equal to the limit, ask for more transaction + * Handles the success request of the transaction, if the amount of transaction is equal to the limit, ask for more transaction * @param answer The answer, this object depends on the kind of request is made to the api * @param idPetition the id of the ApiRequest petition */ @Override public void success(Object answer, int idPetition) { List transfers = (List) answer ; - for(HistoricalTransfer transfer : transfers) { + for(final HistoricalTransfer transfer : transfers) { if (transfer.getOperation() != null){ - CryptoCoinTransaction transaction = new CryptoCoinTransaction(); + final CryptoCoinTransaction transaction = new CryptoCoinTransaction(); transaction.setAccountId(account.getId()); transaction.setAmount(transfer.getOperation().getAssetAmount().getAmount().longValue()); BitsharesAssetInfo info = db.bitsharesAssetDao().getBitsharesAssetInfoById(transfer.getOperation().getAssetAmount().getAsset().getObjectId()); if (info == null) { //The cryptoCurrency is not in the database, queringfor its data - final Object SYNC = new Object(); //Object to syn the answer ApiRequest assetRequest = new ApiRequest(0, new ApiRequestListener() { @Override public void success(Object answer, int idPetition) { @@ -476,39 +544,24 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI info.setCryptoCurrencyId(idCryptoCurrency); asset.setId((int)idCryptoCurrency); db.bitsharesAssetDao().insertBitsharesAssetInfo(info); + saveTransaction(transaction,info,transfer); } - synchronized (SYNC){ - SYNC.notifyAll(); - } + } @Override public void fail(int idPetition) { - synchronized (SYNC){ - SYNC.notifyAll(); - } + //TODO Error } }); ArrayList assets = new ArrayList<>(); assets.add(transfer.getOperation().getAssetAmount().getAsset().getObjectId()); GrapheneApiGenerator.getAssetById(assets,assetRequest); - synchronized (SYNC){ - try {SYNC.wait(60000);} catch (InterruptedException ignore) {} - } - info = db.bitsharesAssetDao().getBitsharesAssetInfoById(transfer.getOperation().getAssetAmount().getAsset().getObjectId()); + }else{ + saveTransaction(transaction,info,transfer); } - if( info == null){ - //We couldn't retrieve the cryptocurrency - return; - } - transaction.setIdCurrency((int)info.getCryptoCurrencyId()); - transaction.setConfirmed(true); //graphene transaction are always confirmed - transaction.setFrom(transfer.getOperation().getFrom().getObjectId()); - transaction.setInput(!transfer.getOperation().getFrom().getObjectId().equals(account.getAccountId())); - transaction.setTo(transfer.getOperation().getTo().getObjectId()); - GrapheneApiGenerator.getBlockHeaderTime(transfer.getBlockNum(), new ApiRequest(0, new GetTransactionDate(transaction, db.transactionDao()))); } } if(transfers.size()>= limit){ @@ -524,6 +577,16 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI public void fail(int idPetition) { } + + private void saveTransaction(CryptoCoinTransaction transaction, BitsharesAssetInfo info, HistoricalTransfer transfer){ + transaction.setIdCurrency((int)info.getCryptoCurrencyId()); + transaction.setConfirmed(true); //graphene transaction are always confirmed + transaction.setFrom(transfer.getOperation().getFrom().getObjectId()); + transaction.setInput(!transfer.getOperation().getFrom().getObjectId().equals(account.getAccountId())); + transaction.setTo(transfer.getOperation().getTo().getObjectId()); + + GrapheneApiGenerator.getBlockHeaderTime(transfer.getBlockNum(), new ApiRequest(0, new GetTransactionDate(transaction, db.transactionDao()))); + } } /** @@ -547,6 +610,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI GrapheneApiGenerator.getEquivalentValue(fromAsset.getBitsharesId(),toAsset.getBitsharesId(), getEquivalentRequest); }else{ //TODO error + System.out.println("Equivalen Value error "); } } @@ -554,13 +618,12 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI * Class to retrieve the account id or the account name, if one of those is missing */ private class AccountIdOrNameListener implements ApiRequestListener{ - final Object SYNC; - boolean ready = false; + final ManagerRequest request; GrapheneAccount account; - AccountIdOrNameListener(Object SYNC) { - this.SYNC = SYNC; + AccountIdOrNameListener(ManagerRequest request) { + this.request = request; } @Override @@ -572,18 +635,12 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI account.setName(props.name); } - synchronized (SYNC){ - ready = true; - SYNC.notifyAll(); - } + request.success(account); } @Override public void fail(int idPetition) { - synchronized (SYNC){ - ready = true; - SYNC.notifyAll(); - } + request.fail(); } } @@ -591,36 +648,27 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI * Class to retrieve the asset id or the asset name, if one of those is missing */ private class AssetIdOrNameListener implements ApiRequestListener{ - final Object SYNC; - boolean ready = false; + final ManagerRequest request; BitsharesAsset asset; - AssetIdOrNameListener(Object SYNC) { - this.SYNC = SYNC; + AssetIdOrNameListener(ManagerRequest request) { + this.request = request; } @Override public void success(Object answer, int idPetition) { if(answer instanceof ArrayList) { - if (((ArrayList) answer).get(0) instanceof BitsharesAsset) { asset = (BitsharesAsset) ((ArrayList) answer).get(0); + request.success(asset); } } - - synchronized (SYNC){ - ready = true; - SYNC.notifyAll(); - } } @Override public void fail(int idPetition) { - synchronized (SYNC){ - ready = true; - SYNC.notifyAll(); - } + //TODO fail asset retrieve } } @@ -637,7 +685,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI */ TransactionDao transactionDao; - public GetTransactionDate(CryptoCoinTransaction transaction, TransactionDao transactionDao) { + GetTransactionDate(CryptoCoinTransaction transaction, TransactionDao transactionDao) { this.transaction = transaction; this.transactionDao = transactionDao; } @@ -645,11 +693,13 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI @Override public void success(Object answer, int idPetition) { if(answer instanceof BlockHeader){ - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + @SuppressLint("SimpleDateFormat") SimpleDateFormat dateFormat = new SimpleDateFormat(SIMPLE_DATE_FORMAT); + dateFormat.setTimeZone(TimeZone.getTimeZone(DEFAULT_TIME_ZONE)); try { transaction.setDate(dateFormat.parse(((BlockHeader) answer).timestamp)); - transactionDao.insertTransaction(transaction); + if (transactionDao.getByTransaction(transaction.getDate(),transaction.getFrom(),transaction.getTo(),transaction.getAmount()) == null) { + transactionDao.insertTransaction(transaction); + } } catch (ParseException e) { e.printStackTrace(); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java b/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java index e1e3b6d..9941e8e 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java @@ -18,14 +18,14 @@ public interface CryptoAccountManager { * @param account The values to be created, * @returnThe CruptoNetAccount created, or null if it couldn't be created */ - public CryptoNetAccount createAccountFromSeed(CryptoNetAccount account, Context context); + public void createAccountFromSeed(CryptoNetAccount account, ManagerRequest request, Context context); /** * Imports a CryptoCoin account from a seed * @param account A CryptoNetAccount with the parameters to be imported * @returnThe CruptoNetAccount imported */ - public CryptoNetAccount importAccountFromSeed(CryptoNetAccount account, Context context); + public void importAccountFromSeed(CryptoNetAccount account, Context context); /** * Loads account data from the database diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/ManagerRequest.java b/app/src/main/java/cy/agorise/crystalwallet/manager/ManagerRequest.java new file mode 100644 index 0000000..cfd6464 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/ManagerRequest.java @@ -0,0 +1,12 @@ +package cy.agorise.crystalwallet.manager; + +/** + * Created by henry on 28/1/2018. + */ + +public interface ManagerRequest { + + public void success(Object answer); + + public void fail(); +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/Contact.java b/app/src/main/java/cy/agorise/crystalwallet/models/Contact.java index 1cc3d6e..be43fb9 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/Contact.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/Contact.java @@ -10,8 +10,11 @@ import android.arch.persistence.room.PrimaryKey; import android.support.annotation.NonNull; import android.support.v7.recyclerview.extensions.DiffCallback; +import java.util.ArrayList; import java.util.List; +import cy.agorise.crystalwallet.enums.CryptoNet; + /** * Represents a user contact * @@ -19,7 +22,7 @@ import java.util.List; */ @Entity(tableName="contact", - indices = {@Index("id"),@Index(value = {"name"}, unique=true)}) + indices = {@Index("id"),@Index(value = {"name"}, unique=true),@Index("email")}) public class Contact { /** @@ -32,6 +35,9 @@ public class Contact { @ColumnInfo(name="name") private String mName; + @ColumnInfo(name="email") + private String mEmail; + @ColumnInfo(name = "gravatar") private String mGravatar; @@ -62,6 +68,14 @@ public class Contact { this.mGravatar = gravatar; } + public String getEmail() { + return this.mEmail; + } + + public void setEmail(String email) { + this.mEmail = email; + } + public int addressesCount(){ return this.mAddresses.size(); } @@ -70,8 +84,30 @@ public class Contact { return this.mAddresses.get(index); } + public void clearAddresses(){ + if (this.mAddresses != null) { + this.mAddresses.clear(); + } + } + public void addAddress(ContactAddress address){ + if (this.mAddresses == null) { + this.mAddresses = new ArrayList(); + } this.mAddresses.add(address); + address.setContactId(this.getId()); + } + + public ContactAddress getCryptoNetAddress(CryptoNet cryptoNet){ + if (this.mAddresses != null) { + for (ContactAddress address : this.mAddresses) { + if (address.getCryptoNet() == cryptoNet) { + return address; + } + } + } + + return null; } public static final DiffCallback DIFF_CALLBACK = new DiffCallback() { diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/ContactAddress.java b/app/src/main/java/cy/agorise/crystalwallet/models/ContactAddress.java index ae247dd..85d9930 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/ContactAddress.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/ContactAddress.java @@ -4,9 +4,12 @@ package cy.agorise.crystalwallet.models; import android.arch.persistence.room.ColumnInfo; import android.arch.persistence.room.Entity; import android.arch.persistence.room.Index; +import android.arch.persistence.room.PrimaryKey; import android.support.annotation.NonNull; import android.support.v7.recyclerview.extensions.DiffCallback; +import cy.agorise.crystalwallet.enums.CryptoNet; + /** * Represents a user contact address * @@ -14,19 +17,37 @@ import android.support.v7.recyclerview.extensions.DiffCallback; */ @Entity(tableName="contact_address", - primaryKeys = {"contact_id","crypto_currency_id"}, - indices = {@Index(value = {"contact_id","crypto_currency_id"}, unique=true)}) + indices = {@Index(value = {"id"}, unique=true),@Index(value = {"contact_id","crypto_net"}, unique=true)}) public class ContactAddress { + /** + * The id on the database + */ + @PrimaryKey(autoGenerate = true) + @ColumnInfo(name = "id") + private long mId; + @ColumnInfo(name = "contact_id") private long mContactId; - @ColumnInfo(name = "crypto_currency_id") - private long mCryptoCurrencyId; + /** + * The crypto net of the address + */ + @NonNull + @ColumnInfo(name = "crypto_net") + private CryptoNet mCryptoNet; @ColumnInfo(name="address") private String mAddress; + public long getId() { + return mId; + } + + public void setId(long id) { + this.mId = id; + } + public long getContactId() { return mContactId; } @@ -35,12 +56,12 @@ public class ContactAddress { this.mContactId = contactId; } - public long getCryptoCurrencyId() { - return mCryptoCurrencyId; + public CryptoNet getCryptoNet() { + return mCryptoNet; } - public void setCryptoCurrencyId(long cryptoCurrencyId) { - this.mCryptoCurrencyId = cryptoCurrencyId; + public void setCryptoNet(CryptoNet cryptoNet) { + this.mCryptoNet = cryptoNet; } public String getAddress() { @@ -56,7 +77,7 @@ public class ContactAddress { public boolean areItemsTheSame( @NonNull ContactAddress oldContactAddress, @NonNull ContactAddress newContactAddress) { return (oldContactAddress.getContactId() == newContactAddress.getContactId()) - && (oldContactAddress.getCryptoCurrencyId() == newContactAddress.getCryptoCurrencyId()); + && (oldContactAddress.getCryptoNet() == newContactAddress.getCryptoNet()); } @Override public boolean areContentsTheSame( @@ -73,7 +94,7 @@ public class ContactAddress { ContactAddress that = (ContactAddress) o; if (mContactId != that.mContactId) return false; - if (mCryptoCurrencyId != that.mCryptoCurrencyId) return false; + if (mCryptoNet != that.mCryptoNet) return false; return mAddress.equals(that.mAddress); } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/GeneralSetting.java b/app/src/main/java/cy/agorise/crystalwallet/models/GeneralSetting.java index b5b322d..822a10e 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/GeneralSetting.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/GeneralSetting.java @@ -17,6 +17,7 @@ public class GeneralSetting { public final static String SETTING_NAME_PREFERED_COUNTRY = "PREFERED_COUNTRY"; public final static String SETTING_NAME_PREFERED_CURRENCY = "PREFERED_CURRENCY"; + public final static String SETTING_PASSWORD = "PASSWORD"; /** * The id on the database diff --git a/app/src/main/java/cy/agorise/crystalwallet/network/BitsharesCryptoNetVerifier.java b/app/src/main/java/cy/agorise/crystalwallet/network/BitsharesCryptoNetVerifier.java new file mode 100644 index 0000000..8a47bce --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/network/BitsharesCryptoNetVerifier.java @@ -0,0 +1,54 @@ +package cy.agorise.crystalwallet.network; + +import cy.agorise.crystalwallet.enums.CryptoNet; +import cy.agorise.graphenej.interfaces.WitnessResponseListener; +import cy.agorise.graphenej.models.BaseResponse; +import cy.agorise.graphenej.models.WitnessResponse; + +/** + * + * Created by henry on 28/2/2018. + */ + +public class BitsharesCryptoNetVerifier extends CryptoNetVerifier { + + + /** + * TODO We need to change this to a type of subCryptoNet + */ + private final CryptoNet cryptoNet = CryptoNet.BITSHARES; + /** + * Todo info need to be on the SubCryptoNet + */ + private final String CHAIN_ID = "9cf6f255a208100d2bb275a3c52f4b1589b7ec9c9bfc2cb2a5fe6411295106d8";//testnet + //private final String CHAIN_ID = "4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8";//mainnet + + @Override + public void checkURL(final String url) { + final long startTime = System.currentTimeMillis(); + WebSocketThread thread = new WebSocketThread(new GetChainId(new WitnessResponseListener() { + @Override + public void onSuccess(WitnessResponse response) { + if(response.result instanceof String) { + if(response.result.equals(CHAIN_ID)) { + CryptoNetManager.verifiedCryptoNetURL(cryptoNet, url, System.currentTimeMillis() - startTime); + }else{ + System.out.println(" BitsharesCryptoNetVerifier Error we are not in the net current chain id " + response.result + " excepted " + CHAIN_ID); + //TODO handle error bad chain + } + } + } + + @Override + public void onError(BaseResponse.Error error) { + //TODO handle error + } + }),url); + thread.start(); + } + + @Override + public String getChainId() { + return CHAIN_ID; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/network/CryptoNetManager.java b/app/src/main/java/cy/agorise/crystalwallet/network/CryptoNetManager.java new file mode 100644 index 0000000..d424cc8 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/network/CryptoNetManager.java @@ -0,0 +1,149 @@ +package cy.agorise.crystalwallet.network; + +import android.support.annotation.NonNull; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; + +import cy.agorise.crystalwallet.enums.CryptoNet; + +/** + * Created by henry on 6/3/2018. + */ + +public abstract class CryptoNetManager { + /** + * This map contains the list of the urls to be tested + */ + private static HashMap> CryptoNetURLs = new HashMap<>(); + + /** + * This map contains the list of urls been tested and ordered by the fastests + */ + private static HashMap> TestedURLs = new HashMap<>(); + + public static String getURL(CryptoNet crypto){ + return CryptoNetManager.getURL(crypto,0); + } + + + public static String getURL(CryptoNet crypto, int index){ + if(TestedURLs.containsKey(crypto) && TestedURLs.get(crypto).size()>index){ + return TestedURLs.get(crypto).get(index).getUrl(); + } + + if(CryptoNetURLs.containsKey(crypto) && !CryptoNetURLs.get(crypto).isEmpty()){ + return CryptoNetURLs.get(crypto).iterator().next(); + } + return null; + } + + public static int getURLSize(CryptoNet crypto){ + if(TestedURLs.containsKey(crypto)){ + return TestedURLs.get(crypto).size(); + } + return 0; + } + + public static void addCryptoNetURL(CryptoNet crypto, String url){ + if(!CryptoNetURLs.containsKey(crypto)){ + CryptoNetURLs.put(crypto,new HashSet()); + } + + CryptoNetURLs.get(crypto).add(url); + CryptoNetVerifier verifier = CryptoNetVerifier.getNetworkVerify(crypto); + if(verifier != null) { + verifier.checkURL(url); + } + } + + public static void addCryptoNetURL(CryptoNet crypto, String[] urls){ + if(!CryptoNetURLs.containsKey(crypto)){ + CryptoNetURLs.put(crypto,new HashSet()); + } + CryptoNetVerifier verifier = CryptoNetVerifier.getNetworkVerify(crypto); + + for(String url : urls) { + CryptoNetURLs.get(crypto).add(url); + if(verifier != null) { + verifier.checkURL(url); + } + } + } + + public static void removeCryptoNetURL(CryptoNet crypto, String url){ + if(CryptoNetURLs.containsKey(crypto)){ + CryptoNetURLs.get(crypto).remove(url); + } + } + + public static void verifiedCryptoNetURL(CryptoNet crypto, String url, long time){ + if(CryptoNetURLs.containsKey(crypto) && CryptoNetURLs.get(crypto).contains(url)){ + if(!TestedURLs.containsKey(crypto)){ + TestedURLs.put(crypto,new ArrayList()); + } + TestedURL testedUrl = new TestedURL(time,url); + if(!TestedURLs.get(crypto).contains(testedUrl)){ + TestedURLs.get(crypto).add(testedUrl); + Collections.sort(TestedURLs.get(crypto)); + } + }else{ + //TODO add error handler + } + } + + public static String getChaindId(CryptoNet crypto){ + CryptoNetVerifier verifier = CryptoNetVerifier.getNetworkVerify(crypto); + if(verifier != null) { + return verifier.getChainId(); + } + return null; + } + + private static class TestedURL implements Comparable{ + private long time; + private String url; + + public TestedURL(long time, String url) { + this.time = time; + this.url = url; + } + + public long getTime() { + return time; + } + + String getUrl() { + return url; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TestedURL)) return false; + + TestedURL testedURL = (TestedURL) o; + + return getUrl().equals(testedURL.getUrl()); + } + + @Override + public int hashCode() { + return getUrl().hashCode(); + } + + @Override + public int compareTo(@NonNull Object o) { + if (this == o) return 0; + if (!(o instanceof TestedURL)) return 0; + + TestedURL testedURL = (TestedURL) o; + return (int) (this.getTime() - testedURL.getTime()); + } + } + +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/network/CryptoNetVerifier.java b/app/src/main/java/cy/agorise/crystalwallet/network/CryptoNetVerifier.java new file mode 100644 index 0000000..7364b79 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/network/CryptoNetVerifier.java @@ -0,0 +1,25 @@ +package cy.agorise.crystalwallet.network; + +import cy.agorise.crystalwallet.enums.CryptoNet; + +/** + * This is used to check if the connection is stable and fast. + * + * Also verifies if the connection with the server is valid. + * + * Created by henry on 28/2/2018. + */ + +public abstract class CryptoNetVerifier { + + static CryptoNetVerifier getNetworkVerify(CryptoNet cryptoNet){ + if(cryptoNet.getLabel().equals(CryptoNet.BITSHARES.getLabel())){ + return new BitsharesCryptoNetVerifier(); + } + return null; + } + + public abstract void checkURL(final String url); + + public abstract String getChainId(); +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/network/GetChainId.java b/app/src/main/java/cy/agorise/crystalwallet/network/GetChainId.java new file mode 100644 index 0000000..2863514 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/network/GetChainId.java @@ -0,0 +1,61 @@ +package cy.agorise.crystalwallet.network; + +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.neovisionaries.ws.client.WebSocket; +import com.neovisionaries.ws.client.WebSocketFrame; + +import java.io.Serializable; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import cy.agorise.graphenej.RPC; +import cy.agorise.graphenej.api.BaseGrapheneHandler; +import cy.agorise.graphenej.interfaces.WitnessResponseListener; +import cy.agorise.graphenej.models.ApiCall; +import cy.agorise.graphenej.models.WitnessResponse; + +/** + * Created by henry on 28/2/2018. + */ + +public class GetChainId extends BaseGrapheneHandler { + + private final WitnessResponseListener mListener; + + public GetChainId(WitnessResponseListener listener) { + super(listener); + this.mListener = listener; + } + + @Override + public void onConnected(WebSocket websocket, Map> headers) throws Exception { + ApiCall getAccountByName = new ApiCall(0, "get_chain_id", new ArrayList(), RPC.VERSION, 1); + websocket.sendText(getAccountByName.toJsonString()); + } + + @Override + public void onTextFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { + System.out.println("<<< "+frame.getPayloadText()); + String response = frame.getPayloadText(); + + Type GetChainIdResponse = new TypeToken>(){}.getType(); + GsonBuilder builder = new GsonBuilder(); + WitnessResponse> witnessResponse = builder.create().fromJson(response, GetChainIdResponse); + if(witnessResponse.error != null){ + this.mListener.onError(witnessResponse.error); + }else{ + this.mListener.onSuccess(witnessResponse); + } + + websocket.disconnect(); + } + + @Override + public void onFrameSent(WebSocket websocket, WebSocketFrame frame) throws Exception { + if(frame.isTextFrame()) + System.out.println(">>> "+frame.getPayloadText()); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/service/CrystalWalletService.java b/app/src/main/java/cy/agorise/crystalwallet/service/CrystalWalletService.java index 5cecb10..93b89a5 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/service/CrystalWalletService.java +++ b/app/src/main/java/cy/agorise/crystalwallet/service/CrystalWalletService.java @@ -109,8 +109,7 @@ public class CrystalWalletService extends LifecycleService { if (LoadEquivalencesThread != null) { LoadEquivalencesThread.stopLoadingEquivalences(); - } - ; + }; LoadEquivalencesThread = new EquivalencesThread(service, generalSetting.getValue(), bitsharesAssets); LoadEquivalencesThread.start(); } @@ -184,14 +183,14 @@ public class CrystalWalletService extends LifecycleService { } //if (LoadEquivalencesThread == null) { - // LoadEquivalencesThread = new Thread() { + // LoadEquivalencesThread = new EquivalencesThread() { // @Override // public void run() { loadEquivalentsValues(); // } // }; // LoadEquivalencesThread.start(); - //} + // } // If we get killed, after returning from here, restart return START_STICKY; diff --git a/app/src/main/java/cy/agorise/crystalwallet/util/CircleTransformation.java b/app/src/main/java/cy/agorise/crystalwallet/util/CircleTransformation.java new file mode 100644 index 0000000..e31ce57 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/util/CircleTransformation.java @@ -0,0 +1,47 @@ +package cy.agorise.crystalwallet.util; + +/** + * Created by Henry Varona on 25/2/2018. + */ + +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; + +import com.squareup.picasso.Transformation; + +public class CircleTransformation implements Transformation { + @Override + public Bitmap transform(Bitmap source) { + int size = Math.min(source.getWidth(), source.getHeight()); + + int x = (source.getWidth() - size) / 2; + int y = (source.getHeight() - size) / 2; + + Bitmap squaredBitmap = Bitmap.createBitmap(source, x, y, size, size); + if (squaredBitmap != source) { + source.recycle(); + } + + Bitmap bitmap = Bitmap.createBitmap(size, size, source.getConfig()); + + Canvas canvas = new Canvas(bitmap); + Paint paint = new Paint(); + BitmapShader shader = new BitmapShader(squaredBitmap, + BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP); + paint.setShader(shader); + paint.setAntiAlias(true); + + float r = size / 2f; + canvas.drawCircle(r, r, r, paint); + + squaredBitmap.recycle(); + return bitmap; + } + + @Override + public String key() { + return "CircleTransformation"; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/util/MD5Hash.java b/app/src/main/java/cy/agorise/crystalwallet/util/MD5Hash.java new file mode 100644 index 0000000..38e6534 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/util/MD5Hash.java @@ -0,0 +1,22 @@ +package cy.agorise.crystalwallet.util; + +/** + * Created by Henry Varona on 24/2/2018. + */ + +public class MD5Hash { + + static public String hash(String md5) { + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); + byte[] array = md.digest(md5.getBytes()); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < array.length; ++i) { + sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1,3)); + } + return sb.toString(); + } catch (java.security.NoSuchAlgorithmException e) { + } + return null; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/util/PasswordManager.java b/app/src/main/java/cy/agorise/crystalwallet/util/PasswordManager.java new file mode 100644 index 0000000..7f52970 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/util/PasswordManager.java @@ -0,0 +1,22 @@ +package cy.agorise.crystalwallet.util; + +/** + * Created by Henry Varona on 29/1/2018. + */ + +public class PasswordManager { + + //TODO implement password checking using the encryption implemented in encriptPassword + public static boolean checkPassword(String encriptedPassword, String passwordToCheck){ + if (encriptedPassword.equals(passwordToCheck)){ + return true; + } else { + return false; + } + } + + //TODO implement password encryption + public static String encriptPassword(String password){ + return password; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java index ec076d9..8a3258b 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java @@ -6,6 +6,7 @@ import android.arch.lifecycle.LiveData; import android.arch.paging.PagedList; import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.enums.CryptoNet; import cy.agorise.crystalwallet.models.Contact; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; @@ -30,7 +31,31 @@ public class ContactListViewModel extends AndroidViewModel { ); } + public void init(){ + contactList = this.db.contactDao().contactsByName().create(0, + new PagedList.Config.Builder() + .setEnablePlaceholders(true) + .setPageSize(10) + .setPrefetchDistance(10) + .build() + ); + } + + public void init(CryptoNet cryptoNet){ + contactList = this.db.contactDao().contactsByNameAndCryptoNet(cryptoNet.name()).create(0, + new PagedList.Config.Builder() + .setEnablePlaceholders(true) + .setPageSize(10) + .setPrefetchDistance(10) + .build() + ); + } + public LiveData> getContactList(){ return this.contactList; } + + public boolean contactExists(String name){ + return this.db.contactDao().existsByName(name); + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactViewModel.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactViewModel.java new file mode 100644 index 0000000..1951215 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactViewModel.java @@ -0,0 +1,70 @@ +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.Contact; +import cy.agorise.crystalwallet.models.ContactAddress; + +/** + * Created by Henry Varona on 2/4/2018. + */ + +public class ContactViewModel extends AndroidViewModel { + + private CrystalDatabase db; + private LiveData contact; + private LiveData> contactAddresses; + + public ContactViewModel(Application application) { + super(application); + this.db = CrystalDatabase.getAppDatabase(application.getApplicationContext()); + } + + public void init(long contactId){ + this.contact = this.db.contactDao().getById(contactId); + this.contactAddresses = this.db.contactDao().getContactAddresses(contactId); + } + + public LiveData getContact(){ + return this.contact; + } + + public LiveData> getContactAddresses(){ + return this.contactAddresses; + } + + public boolean modifyContact(Contact contact){ + this.db.contactDao().update(contact); + + for (int i=0;i 0){ + this.db.contactDao().updateAddresses(nextAddress); + } else { + nextAddress.setContactId(contact.getId()); + this.db.contactDao().addAddresses(nextAddress); + } + } + + return true; + } + + public boolean addContact(Contact contact){ + long newContactId = this.db.contactDao().add(contact)[0]; + boolean contactWasAdded = newContactId >= 0; + + for (int i=0;i getCryptoNetAccount(){ return this.cryptoNetAccount; } - } diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/CreateContactValidator.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/CreateContactValidator.java new file mode 100644 index 0000000..1b84e43 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/CreateContactValidator.java @@ -0,0 +1,23 @@ +package cy.agorise.crystalwallet.viewmodels.validators; + +import android.content.Context; +import android.widget.EditText; + +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.BitsharesAccountNameDoesntExistsValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.ContactNameValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.EmailValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.PinConfirmationValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.PinValidationField; + +/** + * Created by Henry Varona on 2/2/2018. + */ + +public class CreateContactValidator extends UIValidator { + + public CreateContactValidator(Context context, EditText nameEdit, EditText emailEdit){ + super(context); + this.addField(new ContactNameValidationField(nameEdit)); + this.addField(new EmailValidationField(emailEdit)); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/ModifyContactValidator.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/ModifyContactValidator.java new file mode 100644 index 0000000..ec640fd --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/ModifyContactValidator.java @@ -0,0 +1,21 @@ +package cy.agorise.crystalwallet.viewmodels.validators; + +import android.content.Context; +import android.widget.EditText; + +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.ContactNameValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.EmailValidationField; + +/** + * Created by Henry Varona on 2/11/2018. + */ + +public class ModifyContactValidator extends UIValidator { + + public ModifyContactValidator(Context context, Contact contact, EditText nameEdit, EditText emailEdit){ + super(context); + this.addField(new ContactNameValidationField(nameEdit, contact)); + this.addField(new EmailValidationField(emailEdit)); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/PinSecurityValidator.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/PinSecurityValidator.java new file mode 100644 index 0000000..b99f155 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/PinSecurityValidator.java @@ -0,0 +1,23 @@ +package cy.agorise.crystalwallet.viewmodels.validators; + +import android.content.Context; +import android.widget.EditText; + +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.BitsharesAccountNameDoesntExistsValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.CurrentPinValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.PinConfirmationValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.PinValidationField; + +/** + * Created by Henry Varona on 1/28/2018. + */ + +public class PinSecurityValidator extends UIValidator { + + public PinSecurityValidator(Context context, EditText currentPinEdit, EditText newPinEdit, EditText newPinConfirmationEdit){ + super(context); + this.addField(new CurrentPinValidationField(currentPinEdit)); + this.addField(new PinValidationField(newPinEdit)); + this.addField(new PinConfirmationValidationField(newPinEdit,newPinConfirmationEdit)); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/ContactNameValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/ContactNameValidationField.java new file mode 100644 index 0000000..275c550 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/ContactNameValidationField.java @@ -0,0 +1,76 @@ +package cy.agorise.crystalwallet.viewmodels.validators.validationfields; + +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; +import android.widget.EditText; +import android.widget.Spinner; + +import java.util.List; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.cryptonetinforequests.CryptoNetInfoRequestListener; +import cy.agorise.crystalwallet.cryptonetinforequests.CryptoNetInfoRequests; +import cy.agorise.crystalwallet.cryptonetinforequests.ValidateExistBitsharesAccountRequest; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.viewmodels.ContactListViewModel; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; + +/** + * Created by Henry Varona on 2/03/2017. + */ + +public class ContactNameValidationField extends ValidationField { + + private EditText nameField; + private Contact contact; + + public ContactNameValidationField(EditText nameField){ + super(nameField); + this.nameField = nameField; + this.contact = null; + } + + public ContactNameValidationField(EditText nameField, Contact contact){ + super(nameField); + this.nameField = nameField; + this.contact = contact; + } + + public void validate(){ + final String newValue = this.nameField.getText().toString(); + + if (this.contact != null){ + if (this.contact.getName().equals(newValue)){ + this.setLastValue(newValue); + this.startValidating(); + this.setValidForValue(newValue, true); + return; + } + } + + if (!newValue.equals("")) { + if (!newValue.equals(this.getLastValue())) { + this.setLastValue(newValue); + this.startValidating(); + + ContactListViewModel contactListViewModel = ViewModelProviders.of((FragmentActivity) view.getContext()).get(ContactListViewModel.class); + if (contactListViewModel.contactExists(newValue)) { + this.setMessageForValue(newValue, "This name is already used by another contact."); + this.setValidForValue(newValue, false); + } else { + this.setValidForValue(newValue, true); + } + } + } else { + this.setLastValue(""); + this.startValidating(); + this.setMessageForValue("", ""); + this.setValidForValue("", false); + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/CurrentPinValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/CurrentPinValidationField.java new file mode 100644 index 0000000..7877184 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/CurrentPinValidationField.java @@ -0,0 +1,69 @@ +package cy.agorise.crystalwallet.viewmodels.validators.validationfields; + +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.widget.EditText; + +import java.util.List; + +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.util.PasswordManager; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; + +/** + * Created by Henry Varona on 1/28/2018. + */ + +public class CurrentPinValidationField extends ValidationField { + + private EditText currentPinField; + String currentPassword = ""; + + public CurrentPinValidationField(EditText currentPinField){ + super(currentPinField); + this.currentPinField = currentPinField; + GeneralSettingListViewModel generalSettingListViewModel = ViewModelProviders.of((FragmentActivity)view.getContext()).get(GeneralSettingListViewModel.class); + LiveData> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList(); + generalSettingsLiveData.observe((LifecycleOwner) this.view.getContext(), new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_PASSWORD)){ + currentPassword = generalSetting.getValue(); + break; + } + } + } + }); + } + + public void validate(){ + final String newValue = currentPinField.getText().toString(); + + if (this.currentPassword.equals("")) { + this.setLastValue(""); + this.startValidating(); + setValidForValue("",true); + } else if (!newValue.equals(this.getLastValue())) { + this.setLastValue(newValue); + this.startValidating(); + if (newValue.equals("")){ + setMessageForValue(lastValue, ""); + setValidForValue(lastValue, false); + } else { + + if (PasswordManager.checkPassword(this.currentPassword, newValue)) { + setValidForValue(lastValue, true); + } else { + setMessageForValue(lastValue, "Password is invalid."); + setValidForValue(lastValue, false); + } + } + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/EmailValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/EmailValidationField.java new file mode 100644 index 0000000..05c4e51 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/EmailValidationField.java @@ -0,0 +1,49 @@ +package cy.agorise.crystalwallet.viewmodels.validators.validationfields; + +import android.widget.EditText; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import cy.agorise.crystalwallet.R; + +/** + * Created by Henry Varona on 2/21/2017. + */ + +public class EmailValidationField extends ValidationField { + + private EditText emailField; + + public EmailValidationField(EditText emailField){ + super(emailField); + this.emailField = emailField; + } + + public void validate(){ + String newValue = emailField.getText().toString(); + if (!newValue.equals("")) { + + if (!newValue.equals(this.getLastValue())) { + this.setLastValue(newValue); + this.startValidating(); + + String expression = "^[\\w\\.-]+@([\\w\\-]+\\.)+[A-Z]{2,4}$"; + Pattern pattern = Pattern.compile(expression, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(newValue); + + if (!matcher.matches()) { + this.setMessageForValue(newValue, "The email is not valid"); + this.setValidForValue(newValue, false); + } else { + this.setValidForValue(newValue, true); + } + } + } else { + this.setLastValue(""); + this.startValidating(); + this.setMessageForValue("", ""); + this.setValidForValue("", false); + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinConfirmationValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinConfirmationValidationField.java index 00f951e..77b7043 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinConfirmationValidationField.java +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinConfirmationValidationField.java @@ -22,18 +22,24 @@ public class PinConfirmationValidationField extends ValidationField { public void validate(){ String newConfirmationValue = pinConfirmationField.getText().toString(); String newValue = pinField.getText().toString(); - String mixedValue = newValue+"_"+newConfirmationValue; - if (mixedValue != this.getLastValue()) { - this.setLastValue(mixedValue); - this.startValidating(); + String mixedValue = newValue + "_" + newConfirmationValue; - - if (!newConfirmationValue.equals(newValue)){ - this.setMessageForValue(mixedValue,this.validator.getContext().getResources().getString(R.string.mismatch_pin)); - this.setValidForValue(mixedValue,false); - } else { - this.setValidForValue(mixedValue, true); + if (!newConfirmationValue.equals("")) { + if (!mixedValue.equals(this.getLastValue())) { + this.setLastValue(mixedValue); + this.startValidating(); + if (!newConfirmationValue.equals(newValue)) { + this.setMessageForValue(mixedValue, this.validator.getContext().getResources().getString(R.string.mismatch_pin)); + this.setValidForValue(mixedValue, false); + } else { + this.setValidForValue(mixedValue, true); + } } + } else { + this.setLastValue(""); + this.startValidating(); + this.setMessageForValue("", ""); + this.setValidForValue("", false); } } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinValidationField.java index 809b8c6..6228641 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinValidationField.java +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinValidationField.java @@ -20,16 +20,23 @@ public class PinValidationField extends ValidationField { public void validate(){ String newValue = pinField.getText().toString(); - if (newValue != this.getLastValue()) { - this.setLastValue(newValue); - this.startValidating(); + if (!newValue.equals("")) { + if (!newValue.equals(this.getLastValue())) { + this.setLastValue(newValue); + this.startValidating(); - if (newValue.length() < 6) { - this.setMessageForValue(newValue, this.validator.getContext().getResources().getString(R.string.pin_number_warning)); - this.setValidForValue(newValue, false); - } else { - this.setValidForValue(newValue, true); + if (newValue.length() < 6) { + this.setMessageForValue(newValue, this.validator.getContext().getResources().getString(R.string.pin_number_warning)); + this.setValidForValue(newValue, false); + } else { + this.setValidForValue(newValue, true); + } } + } else { + this.setLastValue(""); + this.startValidating(); + this.setMessageForValue("", ""); + this.setValidForValue("", false); } } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactAddressListAdapter.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactAddressListAdapter.java new file mode 100644 index 0000000..1be39eb --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactAddressListAdapter.java @@ -0,0 +1,41 @@ +package cy.agorise.crystalwallet.views; + + +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.ContactAddress; + +/** + * Created by Henry Varona on 2/5/2018. + * + * An adapter to show the elements of a list of contacts addresses + */ + + +public class ContactAddressListAdapter extends ListAdapter { + + public ContactAddressListAdapter() { + super(ContactAddress.DIFF_CALLBACK); + } + + @Override + public ContactAddressViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.contact_address_list_item,parent,false); + + return new ContactAddressViewHolder(v); + } + + @Override + public void onBindViewHolder(ContactAddressViewHolder holder, int position) { + ContactAddress contactAddress = getItem(position); + if (contactAddress != null) { + holder.bindTo(contactAddress); + } else { + holder.clear(); + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactAddressViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactAddressViewHolder.java new file mode 100644 index 0000000..711c9ab --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactAddressViewHolder.java @@ -0,0 +1,111 @@ +package cy.agorise.crystalwallet.views; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.enums.CryptoNet; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.models.ContactAddress; + +/** + * Created by Henry Varona on 2/5/2017. + * + * Represents an element view from the Contact Address List + */ + +public class ContactAddressViewHolder extends RecyclerView.ViewHolder { + private Spinner spCryptoNet; + private EditText etAddress; + private Context context; + private CryptoNet[] cryptoNetArray; + private ArrayAdapter cryptoNetSpinnerAdapter; + + public ContactAddressViewHolder(View itemView) { + super(itemView); + //TODO: use ButterKnife to load this + spCryptoNet = (Spinner) itemView.findViewById(R.id.spCryptoNet); + etAddress = (EditText) itemView.findViewById(R.id.etAddress); + this.context = itemView.getContext(); + + + //load spinners values + cryptoNetArray = CryptoNet.values(); + cryptoNetSpinnerAdapter = new ArrayAdapter( + this.context, + android.R.layout.simple_list_item_1, + cryptoNetArray + ); + spCryptoNet.setAdapter(cryptoNetSpinnerAdapter); + } + + /* + * Clears the information in this element view + */ + public void clear(){ + spCryptoNet.setSelection(0); + etAddress.setText(""); + } + + /* + * Binds this view with the data of an element of the list + */ + public void bindTo(final ContactAddress contactAddress) { + if (contactAddress == null){ + this.clear(); + } else { + etAddress.setText(contactAddress.getAddress()); + + CryptoNet nextCryptoNet; + for (int i=0;i adapterView, View view, int i, long l) { + contactAddress.setCryptoNet(((CryptoNet)spCryptoNet.getSelectedItem())); + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + // + } + }); + etAddress.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + // + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + // + } + + @Override + public void afterTextChanged(Editable editable) { + contactAddress.setAddress(editable.toString()); + } + }); + //etAddress.setText(contactAddress.getAddress()); + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java index e9efce9..854be6b 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java @@ -12,9 +12,7 @@ import android.widget.RelativeLayout; import cy.agorise.crystalwallet.R; import cy.agorise.crystalwallet.models.Contact; -import cy.agorise.crystalwallet.models.CryptoCoinTransaction; import cy.agorise.crystalwallet.viewmodels.ContactListViewModel; -import cy.agorise.crystalwallet.viewmodels.TransactionListViewModel; /** * Created by Henry Varona on 1/15/2018. diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactSelectionListAdapter.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactSelectionListAdapter.java new file mode 100644 index 0000000..58f1350 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactSelectionListAdapter.java @@ -0,0 +1,57 @@ +package cy.agorise.crystalwallet.views; + + +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.Contact; + +/** + * Created by Henry Varona on 2/16/2018. + * + * An adapter to show the elements of a list of contacts to be selected by the user + */ + +public class ContactSelectionListAdapter extends ListAdapter implements ContactSelectionViewHolder.ContactSelectionViewHolderListener { + + private ContactSelectionListAdapterListener listener; + + public ContactSelectionListAdapter() { + super(Contact.DIFF_CALLBACK); + } + + @Override + public ContactSelectionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.contact_selection_list_item,parent,false); + return new ContactSelectionViewHolder(v); + } + + @Override + public void onBindViewHolder(ContactSelectionViewHolder holder, int position) { + Contact contact = getItem(position); + if (contact != null) { + holder.bindTo(contact); + holder.setListener(this); + } else { + holder.clear(); + } + } + + @Override + public void onContactSelected(ContactSelectionViewHolder contactSelectionViewHolder, Contact contact) { + if (this.listener != null){ + this.listener.onContactSelected(contact); + } + } + + public void setListener(ContactSelectionListAdapterListener listener){ + this.listener = listener; + } + + public interface ContactSelectionListAdapterListener{ + public void onContactSelected(Contact contact); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactSelectionViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactSelectionViewHolder.java new file mode 100644 index 0000000..797ae02 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactSelectionViewHolder.java @@ -0,0 +1,93 @@ +package cy.agorise.crystalwallet.views; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.squareup.picasso.Picasso; + +import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.activities.CreateContactActivity; +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.util.CircleTransformation; +import cy.agorise.crystalwallet.util.MD5Hash; + +/** + * Created by Henry Varona on 2/16/2018. + * + * Represents an element view from the Contact Selection List + */ + +public class ContactSelectionViewHolder extends RecyclerView.ViewHolder { + private TextView tvName; + private ImageView ivThumbnail; + private TextView tvLastPaid; + private Context context; + private ContactSelectionViewHolderListener listener; + + public ContactSelectionViewHolder(View itemView) { + super(itemView); + //TODO: use ButterKnife to load this + tvName = (TextView) itemView.findViewById(R.id.tvContactName); + ivThumbnail = (ImageView) itemView.findViewById(R.id.ivContactThumbnail); + tvLastPaid = (TextView) itemView.findViewById(R.id.tvLastPaid); + this.context = itemView.getContext(); + + } + + public void setListener(ContactSelectionViewHolderListener listener){ + this.listener = listener; + } + + /* + * Clears the information in this element view + */ + public void clear(){ + tvName.setText(""); + ivThumbnail.setImageResource(android.R.color.transparent); + tvLastPaid.setText(""); + } + + /* + * Binds this view with the data of an element of the list + */ + public void bindTo(final Contact contact) { + if (contact == null){ + this.clear(); + } else { + final ContactSelectionViewHolder thisViewHolder = this; + + tvName.setText(contact.getName()); + tvLastPaid.setText("Paid: 1 Jan, 2001 01:01"); + + if (contact.getEmail() != null){ + String emailHash = MD5Hash.hash(contact.getEmail()); + String gravatarUrl = "http://www.gravatar.com/avatar/" + emailHash + "?s=204&d=404"; + + Picasso.with(this.context) + .load(gravatarUrl) + .transform(new CircleTransformation()) + .into(ivThumbnail); + } + + this.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (listener != null){ + listener.onContactSelected(thisViewHolder, contact); + } + } + }); + } + } + + public interface ContactSelectionViewHolderListener { + public void onContactSelected(ContactSelectionViewHolder contactSelectionViewHolder, Contact contact); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactViewHolder.java index 429acf0..376ce11 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/ContactViewHolder.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactViewHolder.java @@ -1,13 +1,24 @@ package cy.agorise.crystalwallet.views; import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.provider.ContactsContract; +import android.support.v7.app.AlertDialog; import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.ThemedSpinnerAdapter; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import com.squareup.picasso.Picasso; + import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.activities.CreateContactActivity; +import cy.agorise.crystalwallet.dao.CrystalDatabase; import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.util.CircleTransformation; +import cy.agorise.crystalwallet.util.MD5Hash; /** * Created by Henry Varona on 1/17/2017. @@ -19,6 +30,7 @@ public class ContactViewHolder extends RecyclerView.ViewHolder { private TextView tvName; private ImageView ivThumbnail; private TextView tvLastPaid; + private ImageView ivDeleteContact; private Context context; public ContactViewHolder(View itemView) { @@ -27,6 +39,7 @@ public class ContactViewHolder extends RecyclerView.ViewHolder { tvName = (TextView) itemView.findViewById(R.id.tvContactName); ivThumbnail = (ImageView) itemView.findViewById(R.id.ivContactThumbnail); tvLastPaid = (TextView) itemView.findViewById(R.id.tvLastPaid); + ivDeleteContact = (ImageView) itemView.findViewById(R.id.ivDeleteContact); this.context = itemView.getContext(); } @@ -49,6 +62,47 @@ public class ContactViewHolder extends RecyclerView.ViewHolder { } else { tvName.setText(contact.getName()); tvLastPaid.setText("Paid: 1 Jan, 2001 01:01"); + + this.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(itemView.getContext(), CreateContactActivity.class); + intent.putExtra("CONTACT_ID", contact.getId()); + itemView.getContext().startActivity(intent); + } + }); + + if (contact.getEmail() != null){ + String emailHash = MD5Hash.hash(contact.getEmail()); + String gravatarUrl = "http://www.gravatar.com/avatar/" + emailHash + "?s=204&d=404"; + + Picasso.with(this.context) + .load(gravatarUrl) + .transform(new CircleTransformation()) + .into(ivThumbnail); + } + + this.ivDeleteContact.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + //delete the contact + new AlertDialog.Builder(context) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage("Are you sure you want to delete this contact?") + .setPositiveButton("Yes", new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + CrystalDatabase.getAppDatabase(context).contactDao().deleteContacts(contact); + } + + }) + .setNegativeButton("No", null) + .show(); + + + } + }); } } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceListAdapter.java b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceListAdapter.java index 7554a5d..8d43915 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceListAdapter.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceListAdapter.java @@ -17,16 +17,22 @@ import cy.agorise.crystalwallet.models.CryptoCoinBalance; public class CryptoCoinBalanceListAdapter extends ListAdapter { + CryptoNetBalanceViewHolder cryptoNetBalanceViewHolder; + public CryptoCoinBalanceListAdapter() { super(CryptoCoinBalance.DIFF_CALLBACK); } + public void setCryptoNetBalanceViewHolder(CryptoNetBalanceViewHolder cryptoNetBalanceViewHolder){ + this.cryptoNetBalanceViewHolder = cryptoNetBalanceViewHolder; + } + @Override public CryptoCoinBalanceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.crypto_coin_balance_list_item,parent,false); - return new CryptoCoinBalanceViewHolder(v); + return new CryptoCoinBalanceViewHolder(v, cryptoNetBalanceViewHolder); } @Override diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceListView.java b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceListView.java index 0c3e5ed..df4958f 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceListView.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceListView.java @@ -83,7 +83,7 @@ public class CryptoCoinBalanceListView extends RelativeLayout { * * @param data the list of crypto coin balances that will be show to the user */ - public void setData(List data){ + public void setData(List data, CryptoNetBalanceViewHolder cryptoNetBalanceViewHolder){ //initializes the list adapter if (this.listAdapter == null) { this.listAdapter = new CryptoCoinBalanceListAdapter(); @@ -92,6 +92,7 @@ public class CryptoCoinBalanceListView extends RelativeLayout { //sets the data of the list adapter if (data != null) { + this.listAdapter.setCryptoNetBalanceViewHolder(cryptoNetBalanceViewHolder); this.listAdapter.setList(data); } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java index a17cb05..aee2bdc 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java @@ -39,12 +39,15 @@ public class CryptoCoinBalanceViewHolder extends RecyclerView.ViewHolder { private Context context; - public CryptoCoinBalanceViewHolder(View itemView) { + private CryptoNetBalanceViewHolder cryptoNetBalanceViewHolder; + + public CryptoCoinBalanceViewHolder(View itemView, CryptoNetBalanceViewHolder cryptoNetBalanceViewHolder) { super(itemView); //TODO: use ButterKnife to load this cryptoCoinName = (TextView) itemView.findViewById(R.id.tvCryptoCoinName); cryptoCoinBalance = (TextView) itemView.findViewById(R.id.tvCryptoCoinBalanceAmount); cryptoCoinBalanceEquivalence = (TextView) itemView.findViewById(R.id.tvCryptoCoinBalanceEquivalence); + this.cryptoNetBalanceViewHolder = cryptoNetBalanceViewHolder; this.context = itemView.getContext(); } @@ -80,30 +83,41 @@ public class CryptoCoinBalanceViewHolder extends RecyclerView.ViewHolder { public void onChanged(@Nullable GeneralSetting generalSetting) { if (generalSetting != null) { //Gets the currency object of the preferred currency - CryptoCurrency currencyTo = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getByName(generalSetting.getValue()); + LiveData currencyToLiveData = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getLiveDataByName(generalSetting.getValue()); - //Retrieves the equivalent value of this balance using the "From" currency and the "To" currency - LiveData currencyEquivalenceLiveData = CrystalDatabase.getAppDatabase(context) - .cryptoCurrencyEquivalenceDao().getByFromTo( - currencyTo.getId(), - currencyFrom.getId() - - ); - - //Observes the equivalent value. If the equivalent value changes, this will keep the value showed correct - currencyEquivalenceLiveData.observe((LifecycleOwner) context, new Observer() { + currencyToLiveData.observe((LifecycleOwner) context, new Observer() { @Override - public void onChanged(@Nullable CryptoCurrencyEquivalence cryptoCurrencyEquivalence) { - if (cryptoCurrencyEquivalence != null) { - CryptoCurrency toCurrency = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getById(cryptoCurrencyEquivalence.getFromCurrencyId()); - String equivalenceString = String.format( - "%.2f", - (balance.getBalance()/Math.pow(10,currencyFrom.getPrecision()))/ - (cryptoCurrencyEquivalence.getValue()/Math.pow(10,toCurrency.getPrecision())) - ); + public void onChanged(@Nullable CryptoCurrency cryptoCurrency) { + if (cryptoCurrency != null) { + CryptoCurrency currencyTo = cryptoCurrency; - cryptoCoinBalanceEquivalence.setText( - equivalenceString + " " + toCurrency.getName()); + //Retrieves the equivalent value of this balance using the "From" currency and the "To" currency + LiveData currencyEquivalenceLiveData = CrystalDatabase.getAppDatabase(context) + .cryptoCurrencyEquivalenceDao().getByFromTo( + currencyTo.getId(), + currencyFrom.getId() + + ); + + //Observes the equivalent value. If the equivalent value changes, this will keep the value showed correct + currencyEquivalenceLiveData.observe((LifecycleOwner) context, new Observer() { + @Override + public void onChanged(@Nullable CryptoCurrencyEquivalence cryptoCurrencyEquivalence) { + if (cryptoCurrencyEquivalence != null) { + CryptoCurrency toCurrency = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getById(cryptoCurrencyEquivalence.getFromCurrencyId()); + double equivalentValue = (balance.getBalance() / Math.pow(10, currencyFrom.getPrecision())) / + (cryptoCurrencyEquivalence.getValue() / Math.pow(10, toCurrency.getPrecision())); + String equivalenceString = String.format( + "%.2f", + equivalentValue + ); + + cryptoNetBalanceViewHolder.setEquivalentBalance(balance,equivalentValue); + cryptoCoinBalanceEquivalence.setText( + equivalenceString + " " + toCurrency.getName()); + } + } + }); } } }); diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoNetBalanceViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoNetBalanceViewHolder.java index 5fe8fc2..1753a02 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoNetBalanceViewHolder.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoNetBalanceViewHolder.java @@ -16,6 +16,7 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; +import java.util.HashMap; import java.util.List; import butterknife.BindView; @@ -48,11 +49,18 @@ public class CryptoNetBalanceViewHolder extends RecyclerView.ViewHolder { */ TextView cryptoNetName; + /* + * the view holding the equivalent total of the crypto net user account + */ + TextView cryptoNetEquivalentTotal; + /* * The list view of the crypto coins balances of this crypto net balance */ CryptoCoinBalanceListView cryptoCoinBalanceListView; + HashMap equivalentTotalHashMap; + /* * The button for sending transactions from this crypto net balance account */ @@ -86,6 +94,7 @@ public class CryptoNetBalanceViewHolder extends RecyclerView.ViewHolder { //TODO: use ButterKnife to load the views cryptoNetIcon = (ImageView) itemView.findViewById(R.id.ivCryptoNetIcon); cryptoNetName = (TextView) itemView.findViewById(R.id.tvCryptoNetName); + cryptoNetEquivalentTotal = (TextView) itemView.findViewById(R.id.tvCryptoNetEquivalentTotal); cryptoCoinBalanceListView = (CryptoCoinBalanceListView) itemView.findViewById(R.id.cryptoCoinBalancesListView); btnSendFromThisAccount = (Button) itemView.findViewById(R.id.btnSendFromThisAccount); btnReceiveToThisAccount = (Button) itemView.findViewById(R.id.btnReceiveWithThisAccount); @@ -157,6 +166,23 @@ public class CryptoNetBalanceViewHolder extends RecyclerView.ViewHolder { newFragment.show(ft, "ReceiveDialog"); } + public void setEquivalentBalance(CryptoCoinBalance cryptoCoinBalance, double equivalentValue){ + if (this.equivalentTotalHashMap == null){ + this.equivalentTotalHashMap = new HashMap(); + } + + if (cryptoCoinBalance != null) { + this.equivalentTotalHashMap.put(cryptoCoinBalance, equivalentValue); + float totalEquivalent = 0; + + for (Double nextEquivalent : this.equivalentTotalHashMap.values()){ + totalEquivalent += nextEquivalent; + } + + this.cryptoNetEquivalentTotal.setText(""+totalEquivalent); + } + } + /* * Binds this view with the data of an element of the list */ @@ -164,6 +190,7 @@ public class CryptoNetBalanceViewHolder extends RecyclerView.ViewHolder { if (balance == null){ cryptoNetName.setText("loading..."); } else { + final CryptoNetBalanceViewHolder thisViewHolder = this; this.cryptoNetAccountId = balance.getAccountId(); cryptoNetName.setText(balance.getCryptoNet().getLabel()); @@ -172,13 +199,15 @@ public class CryptoNetBalanceViewHolder extends RecyclerView.ViewHolder { cryptoCoinBalanceListViewModel.init(balance.getAccountId()); LiveData> cryptoCoinBalanceData = cryptoCoinBalanceListViewModel.getCryptoCoinBalanceList(); - cryptoCoinBalanceListView.setData(null); + cryptoCoinBalanceListView.setData(null, this); //Observes the livedata, so any of its changes on the database will be reloaded here cryptoCoinBalanceData.observe((LifecycleOwner)this.itemView.getContext(), new Observer>() { @Override public void onChanged(List cryptoCoinBalances) { - cryptoCoinBalanceListView.setData(cryptoCoinBalances); + cryptoCoinBalanceListView.setData(cryptoCoinBalances, thisViewHolder); + + } }); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/TransactionViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/TransactionViewHolder.java index 364a581..ded083f 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/TransactionViewHolder.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/TransactionViewHolder.java @@ -1,20 +1,30 @@ package cy.agorise.crystalwallet.views; import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.Intent; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.RelativeLayout; import android.widget.TextView; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.util.TimeZone; + import cy.agorise.crystalwallet.R; import cy.agorise.crystalwallet.activities.CryptoCoinTransactionReceiptActivity; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; import cy.agorise.crystalwallet.models.CryptoCurrency; +import cy.agorise.crystalwallet.models.CryptoNetAccount; import cy.agorise.crystalwallet.viewmodels.CryptoCurrencyViewModel; +import cy.agorise.crystalwallet.viewmodels.CryptoNetAccountViewModel; /** * Created by Henry Varona on 17/9/2017. @@ -26,16 +36,18 @@ public class TransactionViewHolder extends RecyclerView.ViewHolder { /* * The view holding the transaction "from" */ - private TextView transactionFrom; + private TextView tvFrom; /* * The view holding the transaction "to" */ - private TextView transactionTo; + private TextView tvTo; /* * The view holding the transaction amount */ - private TextView transactionAmount; + private TextView tvAmount; + private TextView tvEquivalent; private TextView tvTransactionDate; + private TextView tvTransactionHour; private View rootView; private Fragment fragment; @@ -48,10 +60,12 @@ public class TransactionViewHolder extends RecyclerView.ViewHolder { this.cryptoCoinTransactionId = -1; rootView = itemView.findViewById(R.id.rlTransactionItem); - transactionFrom = (TextView) itemView.findViewById(R.id.fromText); - transactionTo = (TextView) itemView.findViewById(R.id.toText); - transactionAmount = (TextView) itemView.findViewById(R.id.amountText); + tvFrom = (TextView) itemView.findViewById(R.id.fromText); + tvTo = (TextView) itemView.findViewById(R.id.toText); + tvAmount = (TextView) itemView.findViewById(R.id.tvAmount); + tvEquivalent = (TextView) itemView.findViewById(R.id.tvEquivalent); tvTransactionDate = (TextView) itemView.findViewById(R.id.tvTransactionDate); + tvTransactionHour = (TextView) itemView.findViewById(R.id.tvTransactionHour); this.fragment = fragment; rootView.setOnClickListener(new View.OnClickListener() { @@ -85,9 +99,12 @@ public class TransactionViewHolder extends RecyclerView.ViewHolder { * Clears all info in this element view */ public void clear(){ - transactionFrom.setText("loading..."); - transactionTo.setText(""); - transactionAmount.setText(""); + tvFrom.setText("loading..."); + tvTo.setText(""); + tvAmount.setText(""); + tvEquivalent.setText(""); + tvTransactionDate.setText(""); + tvTransactionHour.setText(""); } /* @@ -95,28 +112,53 @@ public class TransactionViewHolder extends RecyclerView.ViewHolder { */ public void bindTo(final CryptoCoinTransaction transaction/*, final OnTransactionClickListener listener*/) { if (transaction == null){ - transactionFrom.setText("loading..."); - transactionTo.setText(""); - transactionAmount.setText(""); + clear(); } else { this.cryptoCoinTransactionId = transaction.getId(); CryptoCurrencyViewModel cryptoCurrencyViewModel = ViewModelProviders.of(this.fragment).get(CryptoCurrencyViewModel.class); CryptoCurrency cryptoCurrency = cryptoCurrencyViewModel.getCryptoCurrencyById(transaction.getIdCurrency()); + CryptoNetAccountViewModel cryptoNetAccountViewModel = ViewModelProviders.of(this.fragment).get(CryptoNetAccountViewModel.class); + cryptoNetAccountViewModel.loadCryptoNetAccount(transaction.getAccountId()); + String amountString = String.format("%.2f",transaction.getAmount()/Math.pow(10,cryptoCurrency.getPrecision())); - tvTransactionDate.setText(transaction.getDate().toString()); - transactionFrom.setText(transaction.getFrom()); - transactionTo.setText(transaction.getTo()); + DateFormat dateFormat = new SimpleDateFormat("dd MMM"); + dateFormat.setTimeZone(TimeZone.getTimeZone("cet")); + DateFormat hourFormat = new SimpleDateFormat("HH:mm:ss"); + hourFormat.setTimeZone(TimeZone.getTimeZone("cet")); + tvTransactionDate.setText(dateFormat.format(transaction.getDate())); + tvTransactionHour.setText(hourFormat.format(transaction.getDate())+" CET"); + + tvFrom.setText(transaction.getFrom()); + tvTo.setText(transaction.getTo()); + + LiveData cryptoNetAccountLiveData = cryptoNetAccountViewModel.getCryptoNetAccount(); + + cryptoNetAccountLiveData.observe(this.fragment, new Observer() { + @Override + public void onChanged(@Nullable CryptoNetAccount cryptoNetAccount) { + if (transaction.getInput()){ + tvTo.setText(cryptoNetAccount.getName()); + } else { + tvFrom.setText(cryptoNetAccount.getName()); + } + } + }); + + String finalAmountText = ""; if (transaction.getInput()) { - transactionAmount.setTextColor(itemView.getContext().getResources().getColor(R.color.green)); + tvAmount.setTextColor(itemView.getContext().getResources().getColor(R.color.green)); + finalAmountText = "+ "+amountString + + " " + + cryptoCurrency.getName(); } else { - transactionAmount.setTextColor(itemView.getContext().getResources().getColor(R.color.red)); + tvAmount.setTextColor(itemView.getContext().getResources().getColor(R.color.red)); + finalAmountText = amountString + + " " + + cryptoCurrency.getName(); } - transactionAmount.setText( - amountString - + " " - + cryptoCurrency.getName()); + tvAmount.setText(finalAmountText); //This will load the transaction receipt when the user clicks this view /*itemView.setOnClickListener(new View.OnClickListener() { @Override diff --git a/app/src/main/res/drawable/transaction_list_item_background.xml b/app/src/main/res/drawable/transaction_list_item_background.xml new file mode 100644 index 0000000..2a4b404 --- /dev/null +++ b/app/src/main/res/drawable/transaction_list_item_background.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_create_contact.xml b/app/src/main/res/layout/activity_create_contact.xml new file mode 100644 index 0000000..44df5aa --- /dev/null +++ b/app/src/main/res/layout/activity_create_contact.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + +