Merge branch 'master' into master

This commit is contained in:
Severiano Jaramillo Quintanar 2018-03-13 15:57:47 -06:00 committed by GitHub
commit fcc20711dd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 3261 additions and 438 deletions

View file

@ -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'
}

View file

@ -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\")"
]
}
}

View file

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cy.agorise.crystalwallet">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name="cy.agorise.crystalwallet.application.CrystalApplication"
android:name=".application.CrystalApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="Crystal"
@ -21,7 +22,7 @@
</intent-filter>
</activity>
<activity android:name=".activities.BoardActivity"
android:theme="@style/AppTheme.NoActionBar" > <!-- Dirty trick to avoid toolbar error on balance -->
android:theme="@style/AppTheme.NoActionBar" >
</activity>
<activity android:name=".activities.AccountSeedsManagementActivity" >
</activity>
@ -37,20 +38,27 @@
</activity>
<activity android:name=".activities.BackupSeedActivity" >
</activity>
<activity android:name=".activities.SettingsActivity"
<activity android:name=".activities.PinRequestActivity">
</activity>
<activity
android:name=".activities.SettingsActivity"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustPan">
</activity>
<activity android:name=".activities.AccountsActivity"
android:theme="@style/ActivityDialog"
android:parentActivityName=".activities.BoardActivity" >
<activity
android:name=".activities.AccountsActivity"
android:parentActivityName=".activities.BoardActivity"
android:theme="@style/ActivityDialog">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.BoardActivity" />
</activity>
<service android:name=".service.CrystalWalletService"
<service
android:name=".service.CrystalWalletService"
android:exported="false" />
<activity android:name=".activities.CreateContactActivity"></activity>
</application>
</manifest>

View file

@ -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");
}

View file

@ -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<ContactAddress> 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<Contact> contactLiveData = contactViewModel.getContact();
final CreateContactActivity thisActivity = this;
contactLiveData.observe(this, new Observer<Contact>() {
@Override
public void onChanged(@Nullable Contact contactChanged) {
if (contactChanged != null){
contact = contactChanged;
etName.setText(contact.getName());
etEmail.setText(contact.getEmail());
LiveData<List<ContactAddress>> contactAddresses = contactViewModel.getContactAddresses();
contactAddresses.observe(thisActivity, new Observer<List<ContactAddress>>() {
@Override
public void onChanged(@Nullable List<ContactAddress> 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<ContactAddress>();
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());
}
}
});
}
}

View file

@ -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);

View file

@ -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<List<GeneralSetting>> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList();
generalSettingsLiveData.observe(this, new Observer<List<GeneralSetting>>() {
@Override
public void onChanged(@Nullable List<GeneralSetting> 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();
}
}
}

View file

@ -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<String, 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<RegisterAccountResponse> 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];
}
}
/**

View file

@ -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<String> assets = new ArrayList<>();
assets.add(tOperation.getAssetAmount().getAsset().getObjectId());
GrapheneApiGenerator.getAssetById(assets,assetRequest);
synchronized (SYNC){
try {SYNC.wait(60000);} catch (InterruptedException ignore) {}
}else{
saveTransaction(transaction,(int)info.getCryptoCurrencyId(),accountBitsharesId,tOperation,context);
}
info = bitsharesAssetDao.getBitsharesAssetInfoById(tOperation.getAssetAmount().getAsset().getObjectId());
}
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;

View file

@ -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);
}

View file

@ -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<List<GeneralSetting>> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList();
generalSettingsLiveData.observe(fragmentActivity, new Observer<List<GeneralSetting>>() {
@Override
public void onChanged(@Nullable List<GeneralSetting> 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) {
//
}
}

View file

@ -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<Integer, Contact> 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<Integer, Contact> contactsByNameAndCryptoNet(String cryptoNet);
@Query("SELECT * FROM contact WHERE id = :id")
LiveData<Contact> 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<List<ContactAddress>> 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);
}

View file

@ -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<CryptoCurrency> getByIds(List<Long> ids);
@Query("SELECT * FROM crypto_currency WHERE name = :name")
LiveData<CryptoCurrency> getLiveDataByName(String name);
@Query("SELECT * FROM crypto_currency WHERE name = :name")
CryptoCurrency getByName(String name);

View file

@ -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,

View file

@ -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<CryptoCoinTransaction> 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);

View file

@ -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<PagedList<Contact>> contactsLiveData = contactListViewModel.getContactList();
contactsLiveData.observe(this, new Observer<PagedList<Contact>>() {
@Override
public void onChanged(@Nullable PagedList<Contact> 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();
}
}

View file

@ -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<PagedList<Contact>> contactsLiveData = contactListViewModel.getContactList();
contactsLiveData.observe(this, new Observer<PagedList<Contact>>() {
@Override
public void onChanged(@Nullable PagedList<Contact> contacts) {
contactListView.setData(contacts);
}
});
return v;
}
}

View file

@ -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<String,String> countriesMap;
private GeneralSettingListViewModel generalSettingListViewModel;
private LiveData<List<GeneralSetting>> 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, String>();
String[] countryCodeList = Locale.getISOCountries();
ArrayList<String> countryAndCurrencyList = new ArrayList<String>();
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<String> countryAdapter = new ArrayAdapter<String>(this.getContext(), android.R.layout.simple_spinner_item, countryAndCurrencyList);
spTaxableCountry.setAdapter(countryAdapter);
//Observes the general settings data
generalSettingListLiveData.observe(this, new Observer<List<GeneralSetting>>() {
@Override
public void onChanged(@Nullable List<GeneralSetting> 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<GeneralSetting> generalSettings){
for (GeneralSetting generalSetting:generalSettings) {
if (generalSetting.getName().equals(GeneralSetting.SETTING_NAME_PREFERED_COUNTRY)){
String preferedCountryCode = generalSetting.getValue();
spTaxableCountry.setSelection(((ArrayAdapter<String>)spTaxableCountry.getAdapter()).getPosition(countriesMap.get(preferedCountryCode)));
}
}
}
}

View file

@ -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<List<GeneralSetting>> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList();
pinSecurityValidator = new PinSecurityValidator(this.getContext(), etCurrentPin, etNewPin, etConfirmPin);
pinSecurityValidator.setListener(this);
generalSettingsLiveData.observe(this, new Observer<List<GeneralSetting>>() {
@Override
public void onChanged(@Nullable List<GeneralSetting> 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());
}
}
});
}
}

View file

@ -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<CryptoNetAccount> 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) {

View file

@ -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<CryptoCurrency> 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<List<ContactAddress>> contactAddressesLiveData = contactViewModel.getContactAddresses();
contactAddressesLiveData.observe(this, new Observer<List<ContactAddress>>() {
@Override
public void onChanged(@Nullable List<ContactAddress> 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<assetAdapter.getCount();i++) {
if (assetAdapter.getItem(i).getName().equals(invoice.getCurrency())) {
spAsset.setSelection(i);
break;
}
}
etMemo.setText(invoice.getMemo());
double amount = 0.0;
for (LineItem nextItem : invoice.getLineItems()) {
amount += nextItem.getQuantity() * nextItem.getPrice();
}
DecimalFormat df = new DecimalFormat("####.####");
df.setRoundingMode(RoundingMode.CEILING);
df.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.ENGLISH));
etAmount.setText(df.format(amount));
Log.i("SendFragment",result.getText());
}
}

View file

@ -1,5 +1,6 @@
package cy.agorise.crystalwallet.manager;
import android.annotation.SuppressLint;
import android.content.Context;
import com.google.common.primitives.UnsignedLong;
@ -31,9 +32,11 @@ import cy.agorise.crystalwallet.models.AccountSeed;
import cy.agorise.crystalwallet.models.BitsharesAsset;
import cy.agorise.crystalwallet.models.BitsharesAssetInfo;
import cy.agorise.crystalwallet.models.CryptoCoinTransaction;
import cy.agorise.crystalwallet.models.CryptoCurrency;
import cy.agorise.crystalwallet.models.CryptoNetAccount;
import cy.agorise.crystalwallet.models.GrapheneAccount;
import cy.agorise.crystalwallet.models.GrapheneAccountInfo;
import cy.agorise.crystalwallet.network.CryptoNetManager;
import cy.agorise.graphenej.Address;
import cy.agorise.graphenej.Asset;
import cy.agorise.graphenej.AssetAmount;
@ -54,20 +57,23 @@ import cy.agorise.graphenej.operations.TransferOperationBuilder;
*/
public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetInfoRequestsListener {
private final static String BITSHARES_TESTNET_CHAIN_ID= "9cf6f255a208100d2bb275a3c52f4b1589b7ec9c9bfc2cb2a5fe6411295106d8";
//private final static String BITSHARES_TESTNET_CHAIN_ID= "9cf6f255a208100d2bb275a3c52f4b1589b7ec9c9bfc2cb2a5fe6411295106d8";
private final static String SIMPLE_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
private final static String DEFAULT_TIME_ZONE = "GMT";
@Override
public CryptoNetAccount createAccountFromSeed(CryptoNetAccount account, Context context) {
public void createAccountFromSeed(CryptoNetAccount account, final ManagerRequest request, final Context context) {
if(account instanceof GrapheneAccount) {
GrapheneAccount grapheneAccount = (GrapheneAccount) account;
boolean created = BitsharesFaucetApiGenerator.registerBitsharesAccount(grapheneAccount.getName(),
new Address(ECKey.fromPublicOnly(grapheneAccount.getOwnerKey(context).getPubKey())).toString(),
new Address(ECKey.fromPublicOnly(grapheneAccount.getActiveKey(context).getPubKey())).toString(),
new Address(ECKey.fromPublicOnly(grapheneAccount.getMemoKey(context).getPubKey())).toString(),GrapheneApiGenerator.faucetUrl);
if(created) {
GrapheneAccount fetch = this.getAccountInfoByName(grapheneAccount.getName());
final GrapheneAccount grapheneAccount = (GrapheneAccount) account;
ApiRequest creationRequest = new ApiRequest(1, new ApiRequestListener() {
@Override
public void success(Object answer, int idPetition) {
getAccountInfoByName(grapheneAccount.getName(), new ManagerRequest() {
@Override
public void success(Object answer) {
GrapheneAccount fetch = (GrapheneAccount) answer;
fetch.setSeedId(grapheneAccount.getSeedId());
fetch.setCryptoNet(grapheneAccount.getCryptoNet());
fetch.setAccountIndex(grapheneAccount.getAccountIndex());
@ -76,82 +82,130 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
long idAccount = db.cryptoNetAccountDao().insertCryptoNetAccount(fetch)[0];
fetch.setId(idAccount);
db.grapheneAccountInfoDao().insertGrapheneAccountInfo(new GrapheneAccountInfo(fetch));
GrapheneApiGenerator.subscribeBitsharesAccount(fetch.getId(), fetch.getAccountId(), context);
BitsharesAccountManager.refreshAccountTransactions(fetch.getId(), context);
GrapheneApiGenerator.getAccountBalance(fetch.getId(), fetch.getAccountId(), context);
return fetch;
}
}
return null;
subscribeBitsharesAccount(fetch.getId(),fetch.getAccountId(),context);
request.success(fetch);
}
@Override
public CryptoNetAccount importAccountFromSeed(CryptoNetAccount account, Context context) {
public void fail() {
//TODO get account data fail
}
});
}
@Override
public void fail(int idPetition) {
request.fail();
}
});
BitsharesFaucetApiGenerator.registerBitsharesAccount(grapheneAccount.getName(),
new Address(ECKey.fromPublicOnly(grapheneAccount.getOwnerKey(context).getPubKey())).toString(),
new Address(ECKey.fromPublicOnly(grapheneAccount.getActiveKey(context).getPubKey())).toString(),
new Address(ECKey.fromPublicOnly(grapheneAccount.getMemoKey(context).getPubKey())).toString(),
GrapheneApiGenerator.faucetUrl, creationRequest);
}
}
@Override
public void importAccountFromSeed(CryptoNetAccount account, final Context context) {
if(account instanceof GrapheneAccount) {
GrapheneAccount grapheneAccount = (GrapheneAccount) account;
final GrapheneAccount grapheneAccount = (GrapheneAccount) account;
if(grapheneAccount.getAccountId() == null){
GrapheneAccount fetch = this.getAccountInfoByName(grapheneAccount.getName());
if(fetch == null) {
//TODO grapaheneAccount null, error fetching
return null;
}
this.getAccountInfoByName(grapheneAccount.getName(), new ManagerRequest() {
@Override
public void success(Object answer) {
GrapheneAccount fetch = (GrapheneAccount) answer;
grapheneAccount.setAccountId(fetch.getAccountId());
}else if(grapheneAccount.getName() == null){
GrapheneAccount fetch = this.getAccountInfoById(grapheneAccount.getAccountId());
if(fetch == null) {
//TODO grapaheneAccount null, error fetching
return null;
}
grapheneAccount.setName(fetch.getName());
}
CrystalDatabase db = CrystalDatabase.getAppDatabase(context);
db.cryptoNetAccountDao().insertCryptoNetAccount(grapheneAccount);
db.grapheneAccountInfoDao().insertGrapheneAccountInfo(new GrapheneAccountInfo(grapheneAccount));
GrapheneApiGenerator.subscribeBitsharesAccount(grapheneAccount.getId(), grapheneAccount.getAccountId(), context);
BitsharesAccountManager.refreshAccountTransactions(account.getId(), context);
GrapheneApiGenerator.getAccountBalance(grapheneAccount.getId(), grapheneAccount.getAccountId(), context);
return grapheneAccount;
}
return null;
subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context);
}
@Override
public void loadAccountFromDB(CryptoNetAccount account, Context context) {
if(account instanceof GrapheneAccount){
GrapheneAccount grapheneAccount = (GrapheneAccount) account;
public void fail() {
//TODO get account data fail
}
});
}else if(grapheneAccount.getName() == null){
this.getAccountInfoById(grapheneAccount.getAccountId(), new ManagerRequest() {
@Override
public void success(Object answer) {
GrapheneAccount fetch = (GrapheneAccount) answer;
grapheneAccount.setName(fetch.getName());
CrystalDatabase db = CrystalDatabase.getAppDatabase(context);
GrapheneAccountInfo info = db.grapheneAccountInfoDao().getByAccountId(account.getId());
db.cryptoNetAccountDao().insertCryptoNetAccount(grapheneAccount);
db.grapheneAccountInfoDao().insertGrapheneAccountInfo(new GrapheneAccountInfo(grapheneAccount));
subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context);
}
@Override
public void fail() {
//TODO get account data fail
}
});
}else {
CrystalDatabase db = CrystalDatabase.getAppDatabase(context);
db.cryptoNetAccountDao().insertCryptoNetAccount(grapheneAccount);
db.grapheneAccountInfoDao().insertGrapheneAccountInfo(new GrapheneAccountInfo(grapheneAccount));
subscribeBitsharesAccount(grapheneAccount.getId(), grapheneAccount.getAccountId(), context);
}
}
}
@Override
public void loadAccountFromDB(CryptoNetAccount account, final Context context) {
if(account instanceof GrapheneAccount){
final GrapheneAccount grapheneAccount = (GrapheneAccount) account;
final CrystalDatabase db = CrystalDatabase.getAppDatabase(context);
final GrapheneAccountInfo info = db.grapheneAccountInfoDao().getByAccountId(account.getId());
grapheneAccount.loadInfo(info);
if(grapheneAccount.getAccountId() == null){
GrapheneAccount fetch = this.getAccountInfoByName(grapheneAccount.getName());
if(fetch != null){
this.getAccountInfoByName(grapheneAccount.getName(), new ManagerRequest() {
@Override
public void success(Object answer) {
GrapheneAccount fetch = (GrapheneAccount) answer;
info.setAccountId(fetch.getAccountId());
grapheneAccount.setAccountId(fetch.getAccountId());
db.grapheneAccountInfoDao().insertGrapheneAccountInfo(info);
subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context);
}
@Override
public void fail() {
//TODO account data retrieve failed
}
});
}else if(grapheneAccount.getName() == null){
GrapheneAccount fetch = this.getAccountInfoById(grapheneAccount.getAccountId());
if(fetch != null) {
this.getAccountInfoById(grapheneAccount.getAccountId(), new ManagerRequest() {
@Override
public void success(Object answer) {
GrapheneAccount fetch = (GrapheneAccount) answer;
info.setName(fetch.getName());
grapheneAccount.setName(fetch.getName());
db.grapheneAccountInfoDao().insertGrapheneAccountInfo(info);
subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context);
}
@Override
public void fail() {
//TODO account data retrieve failed
}
});
}else{
subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context);
}
}
}
if(grapheneAccount.getName() == null || grapheneAccount.getAccountId() == null) {
//TODO grapaheneAccount null, error fetching
return;
}
GrapheneApiGenerator.subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context);
BitsharesAccountManager.refreshAccountTransactions(account.getId(),context);
GrapheneApiGenerator.getAccountBalance(grapheneAccount.getId(),grapheneAccount.getAccountId(),context);
}
private void subscribeBitsharesAccount(long accountId, String accountBitsharesID, Context context){
GrapheneApiGenerator.subscribeBitsharesAccount(accountId,accountBitsharesID,context);
BitsharesAccountManager.refreshAccountTransactions(accountId,context);
GrapheneApiGenerator.getAccountBalance(accountId,accountBitsharesID,context);
}
/**
@ -172,6 +226,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
this.validateCreateAccount((ValidateCreateBitsharesAccountRequest) request);
}else{
//TODO not implemented
System.out.println("Error request not implemented " + request.getClass().getName());
}
}
@ -189,7 +244,6 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
if(answer != null && answer instanceof AccountProperties) {
AccountProperties prop = (AccountProperties) answer;
//TODO change the way to compare keys
BrainKey bk = new BrainKey(importRequest.getMnemonic(), 0);
System.out.println(bk.getPublicAddress("BTS").toString());
for(PublicKey activeKey : prop.owner.getKeyAuthList()){
@ -220,12 +274,13 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
GrapheneApiGenerator.getAccountIdByName(importRequest.getAccountName(),checkAccountName);
}
private void validateCreateAccount(ValidateCreateBitsharesAccountRequest createRequest){
private void validateCreateAccount(final ValidateCreateBitsharesAccountRequest createRequest){
// Generate seed or find key
Context context = createRequest.getContext();
AccountSeed seed = AccountSeed.getAccountSeed(SeedType.BIP39, context);
CrystalDatabase db = CrystalDatabase.getAppDatabase(context);
long idSeed = db.accountSeedDao().insertAccountSeed(seed);
assert seed != null;
seed.setId(idSeed);
seed.setName(createRequest.getAccountName());
GrapheneAccount account = new GrapheneAccount();
@ -233,12 +288,20 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
account.setSeedId(idSeed);
account.setAccountIndex(0);
account.setCryptoNet(CryptoNet.BITSHARES);
GrapheneAccount answer =(GrapheneAccount) this.createAccountFromSeed(account,context);
if (answer != null){
this.createAccountFromSeed(account, new ManagerRequest() {
@Override
public void success(Object answer) {
createRequest.setAccountExists(false);
createRequest.setAccount(answer);;
createRequest.setAccount((GrapheneAccount) answer);
}
createRequest.setAccountExists(false);
@Override
public void fail() {
createRequest.setAccountExists(true);
}
}, context);
}
@ -272,12 +335,50 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
*/
private void validateSendRequest(final ValidateBitsharesSendRequest sendRequest) {
//TODO feeAsset
String idAsset = getAssetInfoByName(sendRequest.getAsset());
Asset feeAsset = new Asset(idAsset);
UserAccount fromUserAccount =new UserAccount(sendRequest.getSourceAccount().getAccountId());
CrystalDatabase db = CrystalDatabase.getAppDatabase(sendRequest.getContext());
CryptoCurrency currency = db.cryptoCurrencyDao().getByNameAndCryptoNet(sendRequest.getAsset(), CryptoNet.BITSHARES.name());
if (currency == null){
getAssetInfoByName(sendRequest.getAsset(), new ManagerRequest() {
@Override
public void success(Object answer) {
validateSendRequest(sendRequest, ((BitsharesAsset) answer).getBitsharesId());
}
GrapheneAccount toUserGrapheneAccount = this.getAccountInfoByName(sendRequest.getToAccount());
//TODO bad user to user account
@Override
public void fail() {
}
});
}else{
BitsharesAssetInfo info = db.bitsharesAssetDao().getBitsharesAssetInfo(currency.getId());
if (info == null || info.getBitsharesId() == null || info.getBitsharesId().isEmpty()){
getAssetInfoByName(sendRequest.getAsset(), new ManagerRequest() {
@Override
public void success(Object answer) {
validateSendRequest(sendRequest, ((BitsharesAsset) answer).getBitsharesId());
}
@Override
public void fail() {
}
});
}else {
this.validateSendRequest(sendRequest, info.getBitsharesId());
}
}
}
private void validateSendRequest(final ValidateBitsharesSendRequest sendRequest , final String idAsset){
final Asset feeAsset = new Asset(idAsset);
final UserAccount fromUserAccount =new UserAccount(sendRequest.getSourceAccount().getAccountId());
//TODO cached to accounts
this.getAccountInfoByName(sendRequest.getToAccount(), new ManagerRequest() {
@Override
public void success(Object answer) {
GrapheneAccount toUserGrapheneAccount = (GrapheneAccount) answer;
UserAccount toUserAccount = new UserAccount(toUserGrapheneAccount.getAccountId());
TransferOperationBuilder builder = new TransferOperationBuilder()
.setSource(fromUserAccount)
@ -287,6 +388,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
if(sendRequest.getMemo() != null) {
//builder.setMemo(new Memo(fromUserAccount,toUserAccount,0,sendRequest.getMemo().getBytes()));
//TODO memo
System.out.println("transaction has memo");
}
ArrayList<BaseOperation> operationList = new ArrayList<>();
operationList.add(builder.build());
@ -294,7 +396,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
ECKey privateKey = sendRequest.getSourceAccount().getActiveKey(sendRequest.getContext());
Transaction transaction = new Transaction(privateKey, null, operationList);
transaction.setChainId(BITSHARES_TESTNET_CHAIN_ID);
transaction.setChainId(CryptoNetManager.getChaindId(CryptoNet.BITSHARES));
ApiRequest transactionRequest = new ApiRequest(0, new ApiRequestListener() {
@Override
@ -311,80 +413,47 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI
GrapheneApiGenerator.broadcastTransaction(transaction,feeAsset, transactionRequest);
}
@Override
public void fail() {
//TODO bad user to user account
}
});
}
/**
* 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<String> 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<CryptoCoinTransaction> 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<HistoricalTransfer> transfers = (List<HistoricalTransfer>) 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<String> assets = new ArrayList<>();
assets.add(transfer.getOperation().getAssetAmount().getAsset().getObjectId());
GrapheneApiGenerator.getAssetById(assets,assetRequest);
synchronized (SYNC){
try {SYNC.wait(60000);} catch (InterruptedException ignore) {}
}else{
saveTransaction(transaction,info,transfer);
}
info = db.bitsharesAssetDao().getBitsharesAssetInfoById(transfer.getOperation().getAssetAmount().getAsset().getObjectId());
}
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));
if (transactionDao.getByTransaction(transaction.getDate(),transaction.getFrom(),transaction.getTo(),transaction.getAmount()) == null) {
transactionDao.insertTransaction(transaction);
}
} catch (ParseException e) {
e.printStackTrace();
}

View file

@ -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

View file

@ -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();
}

View file

@ -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<ContactAddress>();
}
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<Contact> DIFF_CALLBACK = new DiffCallback<Contact>() {

View file

@ -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);
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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<CryptoNet,HashSet<String>> CryptoNetURLs = new HashMap<>();
/**
* This map contains the list of urls been tested and ordered by the fastests
*/
private static HashMap<CryptoNet,ArrayList<TestedURL>> 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<String>());
}
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<String>());
}
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 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());
}
}
}

View file

@ -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();
}

View file

@ -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<String, List<String>> headers) throws Exception {
ApiCall getAccountByName = new ApiCall(0, "get_chain_id", new ArrayList<Serializable>(), 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<WitnessResponse<String>>(){}.getType();
GsonBuilder builder = new GsonBuilder();
WitnessResponse<List<String>> 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());
}
}

View file

@ -109,8 +109,7 @@ public class CrystalWalletService extends LifecycleService {
if (LoadEquivalencesThread != null) {
LoadEquivalencesThread.stopLoadingEquivalences();
}
;
};
LoadEquivalencesThread = new EquivalencesThread(service, generalSetting.getValue(), bitsharesAssets);
LoadEquivalencesThread.start();
}
@ -184,7 +183,7 @@ public class CrystalWalletService extends LifecycleService {
}
//if (LoadEquivalencesThread == null) {
// LoadEquivalencesThread = new Thread() {
// LoadEquivalencesThread = new EquivalencesThread() {
// @Override
// public void run() {
loadEquivalentsValues();

View file

@ -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";
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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<PagedList<Contact>> getContactList(){
return this.contactList;
}
public boolean contactExists(String name){
return this.db.contactDao().existsByName(name);
}
}

View file

@ -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> contact;
private LiveData<List<ContactAddress>> 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<Contact> getContact(){
return this.contact;
}
public LiveData<List<ContactAddress>> getContactAddresses(){
return this.contactAddresses;
}
public boolean modifyContact(Contact contact){
this.db.contactDao().update(contact);
for (int i=0;i<contact.addressesCount();i++){
ContactAddress nextAddress = contact.getAddress(i);
if (nextAddress.getId() > 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<contact.addressesCount();i++){
ContactAddress nextAddress = contact.getAddress(i);
nextAddress.setContactId(newContactId);
this.db.contactDao().addAddresses(nextAddress);
}
return contactWasAdded;
}
}

View file

@ -24,7 +24,7 @@ public class CryptoNetAccountViewModel extends AndroidViewModel {
this.db = CrystalDatabase.getAppDatabase(application.getApplicationContext());
}
public void loadCryptoNetAccount(int accountId){
public void loadCryptoNetAccount(long accountId){
this.cryptoNetAccount = this.db.cryptoNetAccountDao().getByIdLiveData(accountId);
}
@ -36,5 +36,4 @@ public class CryptoNetAccountViewModel extends AndroidViewModel {
public LiveData<CryptoNetAccount> getCryptoNetAccount(){
return this.cryptoNetAccount;
}
}

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}
}

View file

@ -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<List<GeneralSetting>> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList();
generalSettingsLiveData.observe((LifecycleOwner) this.view.getContext(), new Observer<List<GeneralSetting>>() {
@Override
public void onChanged(@Nullable List<GeneralSetting> 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);
}
}
}
}
}

View file

@ -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);
}
}
}

View file

@ -23,11 +23,11 @@ public class PinConfirmationValidationField extends ValidationField {
String newConfirmationValue = pinConfirmationField.getText().toString();
String newValue = pinField.getText().toString();
String mixedValue = newValue + "_" + newConfirmationValue;
if (mixedValue != this.getLastValue()) {
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);
@ -35,5 +35,11 @@ public class PinConfirmationValidationField extends ValidationField {
this.setValidForValue(mixedValue, true);
}
}
} else {
this.setLastValue("");
this.startValidating();
this.setMessageForValue("", "");
this.setValidForValue("", false);
}
}
}

View file

@ -20,7 +20,8 @@ public class PinValidationField extends ValidationField {
public void validate(){
String newValue = pinField.getText().toString();
if (newValue != this.getLastValue()) {
if (!newValue.equals("")) {
if (!newValue.equals(this.getLastValue())) {
this.setLastValue(newValue);
this.startValidating();
@ -31,5 +32,11 @@ public class PinValidationField extends ValidationField {
this.setValidForValue(newValue, true);
}
}
} else {
this.setLastValue("");
this.startValidating();
this.setMessageForValue("", "");
this.setValidForValue("", false);
}
}
}

View file

@ -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<ContactAddress, ContactAddressViewHolder> {
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();
}
}
}

View file

@ -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<CryptoNet> 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<CryptoNet>(
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<cryptoNetArray.length;i++){
nextCryptoNet = cryptoNetArray[i];
if (nextCryptoNet.equals(contactAddress.getCryptoNet())){
spCryptoNet.setSelection(i);
break;
}
}
spCryptoNet.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> 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());
}
}
}

View file

@ -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.

View file

@ -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<Contact, ContactSelectionViewHolder> 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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
});
}
}
}

View file

@ -17,16 +17,22 @@ import cy.agorise.crystalwallet.models.CryptoCoinBalance;
public class CryptoCoinBalanceListAdapter extends ListAdapter<CryptoCoinBalance, CryptoCoinBalanceViewHolder> {
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

View file

@ -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<CryptoCoinBalance> data){
public void setData(List<CryptoCoinBalance> 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);
}
}

View file

@ -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,7 +83,13 @@ 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<CryptoCurrency> currencyToLiveData = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getLiveDataByName(generalSetting.getValue());
currencyToLiveData.observe((LifecycleOwner) context, new Observer<CryptoCurrency>() {
@Override
public void onChanged(@Nullable CryptoCurrency cryptoCurrency) {
if (cryptoCurrency != null) {
CryptoCurrency currencyTo = cryptoCurrency;
//Retrieves the equivalent value of this balance using the "From" currency and the "To" currency
LiveData<CryptoCurrencyEquivalence> currencyEquivalenceLiveData = CrystalDatabase.getAppDatabase(context)
@ -96,12 +105,14 @@ public class CryptoCoinBalanceViewHolder extends RecyclerView.ViewHolder {
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",
(balance.getBalance()/Math.pow(10,currencyFrom.getPrecision()))/
(cryptoCurrencyEquivalence.getValue()/Math.pow(10,toCurrency.getPrecision()))
equivalentValue
);
cryptoNetBalanceViewHolder.setEquivalentBalance(balance,equivalentValue);
cryptoCoinBalanceEquivalence.setText(
equivalenceString + " " + toCurrency.getName());
}
@ -112,4 +123,7 @@ public class CryptoCoinBalanceViewHolder extends RecyclerView.ViewHolder {
});
}
}
});
}
}
}

View file

@ -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<CryptoCoinBalance, Double> 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<CryptoCoinBalance, Double>();
}
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<List<CryptoCoinBalance>> 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<List<CryptoCoinBalance>>() {
@Override
public void onChanged(List<CryptoCoinBalance> cryptoCoinBalances) {
cryptoCoinBalanceListView.setData(cryptoCoinBalances);
cryptoCoinBalanceListView.setData(cryptoCoinBalances, thisViewHolder);
}
});
}

View file

@ -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<CryptoNetAccount> cryptoNetAccountLiveData = cryptoNetAccountViewModel.getCryptoNetAccount();
cryptoNetAccountLiveData.observe(this.fragment, new Observer<CryptoNetAccount>() {
@Override
public void onChanged(@Nullable CryptoNetAccount cryptoNetAccount) {
if (transaction.getInput()){
transactionAmount.setTextColor(itemView.getContext().getResources().getColor(R.color.green));
tvTo.setText(cryptoNetAccount.getName());
} else {
transactionAmount.setTextColor(itemView.getContext().getResources().getColor(R.color.red));
tvFrom.setText(cryptoNetAccount.getName());
}
transactionAmount.setText(
amountString
}
});
String finalAmountText = "";
if (transaction.getInput()) {
tvAmount.setTextColor(itemView.getContext().getResources().getColor(R.color.green));
finalAmountText = "+ "+amountString
+ " "
+ cryptoCurrency.getName());
+ cryptoCurrency.getName();
} else {
tvAmount.setTextColor(itemView.getContext().getResources().getColor(R.color.red));
finalAmountText = amountString
+ " "
+ cryptoCurrency.getName();
}
tvAmount.setText(finalAmountText);
//This will load the transaction receipt when the user clicks this view
/*itemView.setOnClickListener(new View.OnClickListener() {
@Override

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<stroke android:color="#5FBD8A" android:width="4dp"/>
</shape>
</item>
<item android:left="3dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="0dp"
android:paddingLeft="0dp"
android:paddingRight="0dp"
android:paddingTop="@dimen/activity_vertical_margin">
<TextView
android:id="@+id/tvName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="10dp"
android:text="Contact Name"
android:textStyle="bold" />
<EditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:background="@drawable/edittext_bg"
android:maxLines="1"
android:textColor="@color/black" />
<TextView
android:id="@+id/tvNameError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:maxLines="1"
android:textColor="@color/red" />
<TextView
android:id="@+id/tvEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="10dp"
android:text="Email"
android:textStyle="bold" />
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:background="@drawable/edittext_bg"
android:maxLines="1"
android:textColor="@color/black" />
<TextView
android:id="@+id/tvEmailError"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:maxLines="1"
android:textColor="@color/red" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvContactAddresses"
android:layout_width="match_parent"
android:layout_height="wrap_content"></android.support.v7.widget.RecyclerView>
<Button
android:id="@+id/btnAddAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/rvContactAddresses"
android:text="Add"/>
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginTop="10dp"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/btnCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="@color/pink"
android:text="@string/cancel"
android:textColor="@color/white" />
<Button
android:id="@+id/btnCreate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="@color/green"
android:padding="10dp"
android:text="Create Contact"
android:textColor="@color/white" />
<Button
android:id="@+id/btnModify"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:background="@color/green"
android:padding="10dp"
android:text="Modify Contact"
android:textColor="@color/white" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:gravity="bottom"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black"></LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="35dp"
android:background="@color/bottomBarColor"
android:gravity="bottom"
android:orientation="horizontal">
<TextView
android:id="@+id/tvAppVersion_brain_key_activity"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="@string/v_1_0_beta" />
<TextView
android:id="@+id/tvBlockNumberHead_brain_key_activity"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:gravity="center"
android:text="@string/block_number" />
<ImageView
android:id="@+id/ivSocketConnected_brain_key_activity"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="0.5" />
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="0.5"
android:src="@drawable/icon_setting"
android:visibility="invisible" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:background="@color/colorPrimary">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:textSize="30dp"
android:padding="10dp"
android:background="@color/colorPrimary"
android:text="Enter Pin:"/>
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberPassword" />
</LinearLayout>
</LinearLayout>

View file

@ -14,7 +14,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true">
<LinearLayout
<RelativeLayout
android:id="@+id/cryptoNetBalanceTitleBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -31,20 +31,30 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:layout_toRightOf="@id/ivCryptoNetIcon"
android:text="unknown coin" />
<TextView
android:id="@+id/tvCryptoNetEquivalentTotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="0.003€" />
<Button
android:id="@+id/btnSendFromThisAccount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="send"/>
android:text="send"
android:visibility="gone" />
<Button
android:id="@+id/btnReceiveWithThisAccount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="receive"/>
</LinearLayout>
android:text="receive"
android:visibility="gone" />
</RelativeLayout>
<cy.agorise.crystalwallet.views.CryptoCoinBalanceListView
android:id="@+id/cryptoCoinBalancesListView"

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="10dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true">
<Spinner
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:id="@+id/spCryptoNet"/>
<EditText
android:id="@+id/etAddress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/spCryptoNet"
android:layout_alignParentEnd="true"
android:textStyle="bold" />
</RelativeLayout>
</LinearLayout>

View file

@ -34,6 +34,14 @@
android:layout_below="@+id/tvContactName"
android:layout_toRightOf="@+id/ivContactThumbnail"
android:text="Paid: Jan 1, 2001, 01:01"
android:textColor="@android:color/darker_gray" />
android:textColor="@android:color/darker_gray"
android:visibility="gone" />
<ImageView
android:id="@+id/ivDeleteContact"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:src="@drawable/deleteicon" />
</RelativeLayout>
</LinearLayout>

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="10dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true">
<ImageView
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:id="@+id/ivContactThumbnail"/>
<TextView
android:id="@+id/tvContactName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@+id/ivContactThumbnail"
android:text="Loading name..."
android:textStyle="bold" />
<TextView
android:id="@+id/tvLastPaid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tvContactName"
android:layout_toRightOf="@+id/ivContactThumbnail"
android:text="Paid: Jan 1, 2001, 01:01"
android:textColor="@android:color/darker_gray" />
</RelativeLayout>
</LinearLayout>

View file

@ -56,9 +56,9 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/tvTaxableCountry"
app:layout_constraintEnd_toEndOf="@id/spBackupAsset"
app:layout_constraintStart_toStartOf="@id/spBackupAsset"
app:layout_constraintEnd_toEndOf="@id/spBackupAsset"/>
app:layout_constraintTop_toBottomOf="@+id/tvTaxableCountry" />
<View
android:id="@+id/vTaxableCountry"

View file

@ -28,6 +28,13 @@
app:layout_constraintTop_toBottomOf="@+id/tvCurrentPin"
tools:ignore="LabelFor" />
<TextView
android:id="@+id/tvCurrentPinError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/red"
app:layout_constraintTop_toBottomOf="@+id/etCurrentPin" />
<EditText
android:id="@+id/etNewPin"
android:layout_width="0dp"
@ -39,7 +46,13 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etCurrentPin" />
app:layout_constraintTop_toBottomOf="@+id/tvCurrentPinError" />
<TextView
android:id="@+id/tvNewPinError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/red"
app:layout_constraintTop_toBottomOf="@+id/etNewPin" />
<EditText
android:id="@+id/etConfirmPin"
@ -52,6 +65,11 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etNewPin" />
app:layout_constraintTop_toBottomOf="@+id/tvNewPinError" />
<TextView
android:id="@+id/tvConfirmPinError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/red"
app:layout_constraintTop_toBottomOf="@+id/etConfirmPin" />
</android.support.constraint.ConstraintLayout>

View file

@ -4,9 +4,22 @@
android:layout_height="match_parent"
tools:context="cy.agorise.crystalwallet.fragments.TransactionsFragment">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!--<TextView
android:id="@+id/tvTransactionSearch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android: />
<Spinner
android:layout_width="match_parent"
android:layout_height="match_parent"></Spinner>
-->
<cy.agorise.crystalwallet.views.TransactionListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/vTransactionListView" />
</RelativeLayout>
</android.support.v4.widget.NestedScrollView>

View file

@ -1,59 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="5dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="10dp">
android:paddingTop="5dp">
<RelativeLayout
android:id="@+id/rlTransactionItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true">
<TextView
android:id="@+id/tvTransactionDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:ems="10"
android:gravity="center"
android:text="date" />
android:layout_alignParentTop="true"
android:background="@drawable/transaction_list_item_background">
<TextView
android:id="@+id/fromText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tvTransactionDate"
android:layout_alignParentStart="true"
android:layout_marginLeft="5dp"
android:layout_toStartOf="@+id/ivArrowFromTo"
android:ems="10"
android:inputType="text"
android:text="from" />
<ImageView
android:id="@+id/ivArrowFromTo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:tint="@color/black"
app:srcCompat="@drawable/ic_arrow_forward" />
<TextView
android:id="@+id/toText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tvTransactionDate"
android:layout_alignParentBottom="false"
android:layout_alignParentRight="true"
android:layout_toEndOf="@+id/ivArrowFromTo"
android:ems="10"
android:inputType="text"
android:text="to"
android:textAlignment="textEnd" />
<Space
android:id="@+id/sAfterFromTo"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/fromText"/>
<TextView
android:id="@+id/amountText"
android:id="@+id/tvTransactionDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/sAfterFromTo"
android:layout_marginLeft="5dp"
android:layout_toStartOf="@+id/ivArrowFromTo"
android:ems="10"
android:text="02 Oct"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvTransactionHour"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tvTransactionDate"
android:layout_marginLeft="5dp"
android:layout_toStartOf="@+id/ivArrowFromTo"
android:ems="10"
android:text="15:01:18 CET" />
<TextView
android:id="@+id/tvAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignWithParentIfMissing="false"
android:layout_below="@+id/fromText"
android:layout_centerHorizontal="true"
android:layout_below="@id/sAfterFromTo"
android:layout_toEndOf="@+id/ivArrowFromTo"
android:ems="10"
android:gravity="center"
android:inputType="text"
android:text="amount" />
android:text="+ 1 BTS"
android:textAlignment="textEnd"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvEquivalent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignWithParentIfMissing="false"
android:layout_below="@id/tvAmount"
android:layout_toEndOf="@+id/ivArrowFromTo"
android:ems="10"
android:inputType="text"
android:text="0.005€"
android:textAlignment="textEnd" />
</RelativeLayout>
</LinearLayout>