From b63feeed17ec34d69589974b3238d7bb4edf91e7 Mon Sep 17 00:00:00 2001 From: henry Date: Sun, 28 Jan 2018 21:18:38 -0400 Subject: [PATCH 01/17] Added ManagerRequest to make the code full async --- .../BitsharesFaucetApiGenerator.java | 44 +-- .../apigenerator/GrapheneApiGenerator.java | 58 ++- .../crystalwallet/dao/TransactionDao.java | 4 + .../manager/BitsharesAccountManager.java | 343 ++++++++++-------- .../manager/CryptoAccountManager.java | 4 +- .../crystalwallet/manager/ManagerRequest.java | 12 + 6 files changed, 244 insertions(+), 221 deletions(-) create mode 100644 app/src/main/java/cy/agorise/crystalwallet/manager/ManagerRequest.java diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java index ec99d7c..8aa4b56 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java @@ -37,8 +37,9 @@ public abstract class BitsharesFaucetApiGenerator { * @param url The url of the faucet * @return The bitshares id of the registered account, or null */ - public static boolean registerBitsharesAccount(String accountName, String ownerKey, - String activeKey, String memoKey, String url){ + public static void registerBitsharesAccount(String accountName, String ownerKey, + String activeKey, String memoKey, String url, + final ApiRequest request){ CreateAccountPetition petition = new CreateAccountPetition(); final Account account = new Account(); account.name=accountName; @@ -62,8 +63,7 @@ public abstract class BitsharesFaucetApiGenerator { HashMap hashMap = new HashMap<>(); hashMap.put("account", hm); - final boolean[] answer = {false}; - final Object SYNC = new Object(); + try { ServiceGenerator sg = new ServiceGenerator(url); IWebService service = sg.getService(IWebService.class); @@ -78,56 +78,36 @@ public abstract class BitsharesFaucetApiGenerator { if (resp.account != null) { try { if(resp.account.name.equals(account.name)) { - synchronized (SYNC){ - answer[0] = true; - SYNC.notifyAll(); - } + request.getListener().success(true,request.getId()); }else{ - System.out.println("ERROR account name different" + resp.account.name); - //ERROR - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } } catch (Exception e) { e.printStackTrace(); - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } }else{ System.out.println("ERROR response doesn't have account " + response.message()); - //ERROR - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } }else{ System.out.println("ERROR fetching info"); - //ERROR - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } } @Override public void onFailure(Call call, Throwable t) { t.printStackTrace(); - synchronized (SYNC) { - SYNC.notifyAll(); - } + request.getListener().fail(request.getId()); } }); - synchronized (SYNC) { - SYNC.wait(60000); - } } catch (Exception e) { e.printStackTrace(); - } + request.getListener().fail(request.getId()); - return answer[0]; + } } /** diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java index 15ea05f..8272895 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java @@ -12,8 +12,6 @@ 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.models.BitsharesAsset; import cy.agorise.crystalwallet.models.BitsharesAssetInfo; import cy.agorise.crystalwallet.models.CryptoCoinBalance; @@ -43,11 +41,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; @@ -353,12 +349,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 +368,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 +387,21 @@ public abstract class GrapheneApiGenerator { info.setCryptoCurrencyId(idCryptoCurrency); asset.setId((int)idCryptoCurrency); bitsharesAssetDao.insertBitsharesAssetInfo(info); - } - synchronized (SYNC){ - SYNC.notifyAll(); + saveTransaction(transaction,(int)info.getCryptoCurrencyId(),accountBitsharesId,tOperation,context); } } @Override public void fail(int idPetition) { - synchronized (SYNC){ - SYNC.notifyAll(); - } + //TODO error retrieving asset } }); ArrayList assets = new ArrayList<>(); assets.add(tOperation.getAssetAmount().getAsset().getObjectId()); GrapheneApiGenerator.getAssetById(assets,assetRequest); - - synchronized (SYNC){ - try {SYNC.wait(60000);} catch (InterruptedException ignore) {} - } - info = bitsharesAssetDao.getBitsharesAssetInfoById(tOperation.getAssetAmount().getAsset().getObjectId()); + }else{ + saveTransaction(transaction,(int)info.getCryptoCurrencyId(),accountBitsharesId,tOperation,context); } - if( info == null){ - //We couldn't retrieve the cryptocurrency - return; - } - transaction.setIdCurrency((int)info.getCryptoCurrencyId()); - transaction.setConfirmed(true); //graphene transaction are always confirmed - transaction.setFrom(tOperation.getFrom().getObjectId()); - transaction.setInput(!tOperation.getFrom().getObjectId().equals(accountBitsharesId)); - transaction.setTo(tOperation.getTo().getObjectId()); - transaction.setDate(new Date()); - transactionDao.insertTransaction(transaction); - //GrapheneApiGenerator.getBlockHeaderTime(, new ApiRequest(0, new BitsharesAccountManager.GetTransactionDate(transaction, db.transactionDao()))); } } } @@ -449,6 +423,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 */ diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java index 5b6673a..151b9f9 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java @@ -7,6 +7,7 @@ import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; import android.arch.persistence.room.Query; +import java.util.Date; import java.util.List; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; @@ -32,6 +33,9 @@ public interface TransactionDao { @Query("SELECT * FROM crypto_coin_transaction WHERE id = :id") LiveData getById(long id); + @Query("SELECT * FROM crypto_coin_transaction WHERE date = :date and 'from' = :from and 'to' = :to and amount = :amount ") + CryptoCoinTransaction getByTransaction(Date date, String from, String to, long amount); + @Insert(onConflict = OnConflictStrategy.REPLACE) public long[] insertTransaction(CryptoCoinTransaction... transactions); diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java b/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java index b7757bc..36048fc 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java @@ -57,103 +57,151 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI private final static String BITSHARES_TESTNET_CHAIN_ID= "9cf6f255a208100d2bb275a3c52f4b1589b7ec9c9bfc2cb2a5fe6411295106d8"; @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(), + 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()); + + CrystalDatabase db = CrystalDatabase.getAppDatabase(context); + long idAccount = db.cryptoNetAccountDao().insertCryptoNetAccount(fetch)[0]; + fetch.setId(idAccount); + db.grapheneAccountInfoDao().insertGrapheneAccountInfo(new GrapheneAccountInfo(fetch)); + subscribeBitsharesAccount(fetch.getId(),fetch.getAccountId(),context); + request.success(fetch); + } + + @Override + 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); - - if(created) { - GrapheneAccount fetch = this.getAccountInfoByName(grapheneAccount.getName()); - fetch.setSeedId(grapheneAccount.getSeedId()); - fetch.setCryptoNet(grapheneAccount.getCryptoNet()); - fetch.setAccountIndex(grapheneAccount.getAccountIndex()); - - CrystalDatabase db = CrystalDatabase.getAppDatabase(context); - 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; - } + new Address(ECKey.fromPublicOnly(grapheneAccount.getMemoKey(context).getPubKey())).toString(), + GrapheneApiGenerator.faucetUrl, creationRequest); } - return null; } @Override - public CryptoNetAccount importAccountFromSeed(CryptoNetAccount account, Context context) { + 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; - } - grapheneAccount.setAccountId(fetch.getAccountId()); + this.getAccountInfoByName(grapheneAccount.getName(), new ManagerRequest() { + @Override + public void success(Object answer) { + GrapheneAccount fetch = (GrapheneAccount) answer; + grapheneAccount.setAccountId(fetch.getAccountId()); + CrystalDatabase db = CrystalDatabase.getAppDatabase(context); + 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 if(grapheneAccount.getName() == null){ - GrapheneAccount fetch = this.getAccountInfoById(grapheneAccount.getAccountId()); - if(fetch == null) { - //TODO grapaheneAccount null, error fetching - return null; - } - grapheneAccount.setName(fetch.getName()); + 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); + 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); } - - 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; } @Override - public void loadAccountFromDB(CryptoNetAccount account, Context context) { + public void loadAccountFromDB(CryptoNetAccount account, final Context context) { if(account instanceof GrapheneAccount){ - GrapheneAccount grapheneAccount = (GrapheneAccount) account; - CrystalDatabase db = CrystalDatabase.getAppDatabase(context); - GrapheneAccountInfo info = db.grapheneAccountInfoDao().getByAccountId(account.getId()); + 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){ - info.setAccountId(fetch.getAccountId()); - grapheneAccount.setAccountId(fetch.getAccountId()); - db.grapheneAccountInfoDao().insertGrapheneAccountInfo(info); - } + 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) { - info.setName(fetch.getName()); - grapheneAccount.setName(fetch.getName()); - db.grapheneAccountInfoDao().insertGrapheneAccountInfo(info); - } - } + 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); + } - if(grapheneAccount.getName() == null || grapheneAccount.getAccountId() == null) { - //TODO grapaheneAccount null, error fetching - return; + @Override + public void fail() { + //TODO account data retrieve failed + } + }); + }else{ + subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context); } - - 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); + } + /** * Process the bitshares manager request * @param request The request Object @@ -189,7 +237,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,7 +267,7 @@ 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); @@ -233,12 +280,19 @@ 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){ - createRequest.setAccountExists(false); - createRequest.setAccount(answer);; - } - createRequest.setAccountExists(false); + this.createAccountFromSeed(account,new ManagerRequest(){ + + @Override + public void success(Object answer) { + createRequest.setAccountExists(false); + createRequest.setAccount((GrapheneAccount)answer); + } + + @Override + public void fail() { + createRequest.setAccountExists(true); + } + },context ); } @@ -272,95 +326,79 @@ 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()); + final String idAsset = getAssetInfoByName(sendRequest.getAsset()); + final Asset feeAsset = new Asset(idAsset); + final UserAccount fromUserAccount =new UserAccount(sendRequest.getSourceAccount().getAccountId()); - GrapheneAccount toUserGrapheneAccount = this.getAccountInfoByName(sendRequest.getToAccount()); - //TODO bad user to user account - UserAccount toUserAccount = new UserAccount(toUserGrapheneAccount.getAccountId()); - TransferOperationBuilder builder = new TransferOperationBuilder() - .setSource(fromUserAccount) - .setDestination(toUserAccount) - .setTransferAmount(new AssetAmount(UnsignedLong.valueOf(sendRequest.getAmount()), new Asset(idAsset))) - .setFee(new AssetAmount(UnsignedLong.valueOf(0), feeAsset)); - if(sendRequest.getMemo() != null) { - //builder.setMemo(new Memo(fromUserAccount,toUserAccount,0,sendRequest.getMemo().getBytes())); - //TODO memo - } - ArrayList operationList = new ArrayList<>(); - operationList.add(builder.build()); - - ECKey privateKey = sendRequest.getSourceAccount().getActiveKey(sendRequest.getContext()); - - Transaction transaction = new Transaction(privateKey, null, operationList); - transaction.setChainId(BITSHARES_TESTNET_CHAIN_ID); - - ApiRequest transactionRequest = new ApiRequest(0, new ApiRequestListener() { + //TODO cached to accounts + this.getAccountInfoByName(sendRequest.getToAccount(), new ManagerRequest() { @Override - public void success(Object answer, int idPetition) { - sendRequest.setSend(true); + public void success(Object answer) { + GrapheneAccount toUserGrapheneAccount = (GrapheneAccount) answer; + UserAccount toUserAccount = new UserAccount(toUserGrapheneAccount.getAccountId()); + TransferOperationBuilder builder = new TransferOperationBuilder() + .setSource(fromUserAccount) + .setDestination(toUserAccount) + .setTransferAmount(new AssetAmount(UnsignedLong.valueOf(sendRequest.getAmount()), new Asset(idAsset))) + .setFee(new AssetAmount(UnsignedLong.valueOf(0), feeAsset)); + if(sendRequest.getMemo() != null) { + //builder.setMemo(new Memo(fromUserAccount,toUserAccount,0,sendRequest.getMemo().getBytes())); + //TODO memo + } + ArrayList operationList = new ArrayList<>(); + operationList.add(builder.build()); + + ECKey privateKey = sendRequest.getSourceAccount().getActiveKey(sendRequest.getContext()); + + Transaction transaction = new Transaction(privateKey, null, operationList); + transaction.setChainId(BITSHARES_TESTNET_CHAIN_ID); + + ApiRequest transactionRequest = new ApiRequest(0, new ApiRequestListener() { + @Override + public void success(Object answer, int idPetition) { + sendRequest.setSend(true); + } + + @Override + public void fail(int idPetition) { + sendRequest.setSend(false); + } + }); + + GrapheneApiGenerator.broadcastTransaction(transaction,feeAsset, transactionRequest); } @Override - public void fail(int idPetition) { - sendRequest.setSend(false); + public void fail() { + //TODO bad user to user account } }); - GrapheneApiGenerator.broadcastTransaction(transaction,feeAsset, transactionRequest); } /** * Returns the account info from a graphene id * @param grapheneId The graphene id of the account */ - private GrapheneAccount getAccountInfoById(String grapheneId){ - final Object SYNC = new Object(); - long timeout = 60000; + private void getAccountInfoById(String grapheneId, ManagerRequest request){ - AccountIdOrNameListener listener = new AccountIdOrNameListener(SYNC); + AccountIdOrNameListener listener = new AccountIdOrNameListener(request); - ApiRequest request = new ApiRequest(0, listener); - GrapheneApiGenerator.getAccountById(grapheneId,request); - - long cTime = System.currentTimeMillis(); - - while(!listener.ready && (System.currentTimeMillis()-cTime) < timeout){ - synchronized (SYNC){ - try { - SYNC.wait(100); - } catch (InterruptedException ignore) {} - } - } - - return listener.account; + ApiRequest accountRequest = new ApiRequest(0, listener); + GrapheneApiGenerator.getAccountById(grapheneId,accountRequest); } /** * Gets account info by its name * @param grapheneName The name of the account to retrieve */ - private GrapheneAccount getAccountInfoByName(String grapheneName){ - final Object SYNC = new Object(); - long timeout = 60000; + private void getAccountInfoByName(String grapheneName, ManagerRequest request){ - AccountIdOrNameListener listener = new AccountIdOrNameListener(SYNC); + AccountIdOrNameListener listener = new AccountIdOrNameListener(request); - ApiRequest request = new ApiRequest(0, listener); - GrapheneApiGenerator.getAccountByName(grapheneName,request); + ApiRequest accountRequest = new ApiRequest(0, listener); + GrapheneApiGenerator.getAccountByName(grapheneName,accountRequest); - long cTime = System.currentTimeMillis(); - - while(!listener.ready && (System.currentTimeMillis()-cTime) < timeout){ - synchronized (SYNC){ - try { - SYNC.wait(100); - } catch (InterruptedException ignore) {} - } - } - - return listener.account; } //TODO expand function to be more generic @@ -554,13 +592,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 +609,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(); } } @@ -649,7 +680,9 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); try { transaction.setDate(dateFormat.parse(((BlockHeader) answer).timestamp)); - transactionDao.insertTransaction(transaction); + if (transactionDao.getByTransaction(transaction.getDate(),transaction.getFrom(),transaction.getTo(),transaction.getAmount()) == null) { + transactionDao.insertTransaction(transaction); + } } catch (ParseException e) { e.printStackTrace(); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java b/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java index e1e3b6d..9941e8e 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java @@ -18,14 +18,14 @@ public interface CryptoAccountManager { * @param account The values to be created, * @returnThe CruptoNetAccount created, or null if it couldn't be created */ - public CryptoNetAccount createAccountFromSeed(CryptoNetAccount account, Context context); + public void createAccountFromSeed(CryptoNetAccount account, ManagerRequest request, Context context); /** * Imports a CryptoCoin account from a seed * @param account A CryptoNetAccount with the parameters to be imported * @returnThe CruptoNetAccount imported */ - public CryptoNetAccount importAccountFromSeed(CryptoNetAccount account, Context context); + public void importAccountFromSeed(CryptoNetAccount account, Context context); /** * Loads account data from the database diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/ManagerRequest.java b/app/src/main/java/cy/agorise/crystalwallet/manager/ManagerRequest.java new file mode 100644 index 0000000..cfd6464 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/ManagerRequest.java @@ -0,0 +1,12 @@ +package cy.agorise.crystalwallet.manager; + +/** + * Created by henry on 28/1/2018. + */ + +public interface ManagerRequest { + + public void success(Object answer); + + public void fail(); +} From 9c62f7a422e8a414c018f122263d6e734c1ed7ca Mon Sep 17 00:00:00 2001 From: henry Date: Mon, 29 Jan 2018 20:30:15 -0400 Subject: [PATCH 02/17] Change Sync funciton to Async functions --- .../manager/BitsharesAccountManager.java | 166 ++++++++++-------- 1 file changed, 91 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java b/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java index 36048fc..da9b514 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java @@ -1,5 +1,6 @@ package cy.agorise.crystalwallet.manager; +import android.annotation.SuppressLint; import android.content.Context; import com.google.common.primitives.UnsignedLong; @@ -31,6 +32,7 @@ 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; @@ -56,6 +58,9 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI 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 void createAccountFromSeed(CryptoNetAccount account, final ManagerRequest request, final Context context) { if(account instanceof GrapheneAccount) { @@ -220,6 +225,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()); } } @@ -273,26 +279,28 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI 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(); account.setName(createRequest.getAccountName()); account.setSeedId(idSeed); - account.setAccountIndex(0); - account.setCryptoNet(CryptoNet.BITSHARES); - this.createAccountFromSeed(account,new ManagerRequest(){ + account.setAccountIndex(0); + account.setCryptoNet(CryptoNet.BITSHARES); + this.createAccountFromSeed(account, new ManagerRequest() { - @Override - public void success(Object answer) { - createRequest.setAccountExists(false); - createRequest.setAccount((GrapheneAccount)answer); - } + @Override + public void success(Object answer) { + createRequest.setAccountExists(false); + createRequest.setAccount((GrapheneAccount) answer); + } + + @Override + public void fail() { + createRequest.setAccountExists(true); + } + }, context); - @Override - public void fail() { - createRequest.setAccountExists(true); - } - },context ); } @@ -324,14 +332,49 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI /** * Broadcast a transaction request */ - private void validateSendRequest(final ValidateBitsharesSendRequest sendRequest){ + private void validateSendRequest(final ValidateBitsharesSendRequest sendRequest) { //TODO feeAsset - final String idAsset = getAssetInfoByName(sendRequest.getAsset()); + 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()); + } + + @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; @@ -344,6 +387,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 operationList = new ArrayList<>(); operationList.add(builder.build()); @@ -402,27 +446,13 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI } //TODO expand function to be more generic - private String getAssetInfoByName(String assetName){ - final Object SYNC = new Object(); - long timeout = 60000; + private void getAssetInfoByName(String assetName, ManagerRequest request){ - AssetIdOrNameListener nameListener = new AssetIdOrNameListener(SYNC); - ApiRequest request = new ApiRequest(0, nameListener); + AssetIdOrNameListener nameListener = new AssetIdOrNameListener(request); + ApiRequest assetRequest = new ApiRequest(0, nameListener); ArrayList assetNames = new ArrayList<>(); assetNames.add(assetName); - GrapheneApiGenerator.getAssetByName(assetNames, request); - - long cTime = System.currentTimeMillis(); - - while(!nameListener.ready && (System.currentTimeMillis()-cTime) < timeout){ - synchronized (SYNC){ - try { - SYNC.wait(100); - } catch (InterruptedException ignore) {} - } - } - - return nameListener.asset.getBitsharesId(); + GrapheneApiGenerator.getAssetByName(assetNames, assetRequest); } @@ -431,7 +461,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI * @param idAccount database id of the account * @param context The android context of this application */ - public static void refreshAccountTransactions(long idAccount, Context context){ + private static void refreshAccountTransactions(long idAccount, Context context){ CrystalDatabase db = CrystalDatabase.getAppDatabase(context); List transactions = db.transactionDao().getByIdAccount(idAccount); CryptoNetAccount account = db.cryptoNetAccountDao().getById(idAccount); @@ -487,23 +517,22 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI } /** - * Handles the succes request of the transaction, if the amount of transaction is equal to the limit, ask for more transaction + * Handles the success request of the transaction, if the amount of transaction is equal to the limit, ask for more transaction * @param answer The answer, this object depends on the kind of request is made to the api * @param idPetition the id of the ApiRequest petition */ @Override public void success(Object answer, int idPetition) { List transfers = (List) answer ; - for(HistoricalTransfer transfer : transfers) { + for(final HistoricalTransfer transfer : transfers) { if (transfer.getOperation() != null){ - CryptoCoinTransaction transaction = new CryptoCoinTransaction(); + final CryptoCoinTransaction transaction = new CryptoCoinTransaction(); transaction.setAccountId(account.getId()); transaction.setAmount(transfer.getOperation().getAssetAmount().getAmount().longValue()); BitsharesAssetInfo info = db.bitsharesAssetDao().getBitsharesAssetInfoById(transfer.getOperation().getAssetAmount().getAsset().getObjectId()); if (info == null) { //The cryptoCurrency is not in the database, queringfor its data - final Object SYNC = new Object(); //Object to syn the answer ApiRequest assetRequest = new ApiRequest(0, new ApiRequestListener() { @Override public void success(Object answer, int idPetition) { @@ -514,39 +543,24 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI info.setCryptoCurrencyId(idCryptoCurrency); asset.setId((int)idCryptoCurrency); db.bitsharesAssetDao().insertBitsharesAssetInfo(info); + saveTransaction(transaction,info,transfer); } - synchronized (SYNC){ - SYNC.notifyAll(); - } + } @Override public void fail(int idPetition) { - synchronized (SYNC){ - SYNC.notifyAll(); - } + //TODO Error } }); ArrayList assets = new ArrayList<>(); assets.add(transfer.getOperation().getAssetAmount().getAsset().getObjectId()); GrapheneApiGenerator.getAssetById(assets,assetRequest); - synchronized (SYNC){ - try {SYNC.wait(60000);} catch (InterruptedException ignore) {} - } - info = db.bitsharesAssetDao().getBitsharesAssetInfoById(transfer.getOperation().getAssetAmount().getAsset().getObjectId()); + }else{ + saveTransaction(transaction,info,transfer); } - if( info == null){ - //We couldn't retrieve the cryptocurrency - return; - } - transaction.setIdCurrency((int)info.getCryptoCurrencyId()); - transaction.setConfirmed(true); //graphene transaction are always confirmed - transaction.setFrom(transfer.getOperation().getFrom().getObjectId()); - transaction.setInput(!transfer.getOperation().getFrom().getObjectId().equals(account.getAccountId())); - transaction.setTo(transfer.getOperation().getTo().getObjectId()); - GrapheneApiGenerator.getBlockHeaderTime(transfer.getBlockNum(), new ApiRequest(0, new GetTransactionDate(transaction, db.transactionDao()))); } } if(transfers.size()>= limit){ @@ -562,6 +576,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()))); + } } /** @@ -585,6 +609,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI GrapheneApiGenerator.getEquivalentValue(fromAsset.getBitsharesId(),toAsset.getBitsharesId(), getEquivalentRequest); }else{ //TODO error + System.out.println("Equivalen Value error "); } } @@ -622,36 +647,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 } } @@ -668,7 +684,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; } @@ -676,8 +692,8 @@ 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) { From c5f2ff21c736d0e2473cf2c0f3e2ecece7f19190 Mon Sep 17 00:00:00 2001 From: Javier Varona Date: Tue, 30 Jan 2018 21:44:37 -0400 Subject: [PATCH 03/17] - Pin settings is now working - Pin lock screen is almost done (work in progress...) --- app/src/main/AndroidManifest.xml | 2 + .../activities/PinRequestActivity.java | 42 +++++ .../application/CrystalApplication.java | 2 + .../application/CrystalSecurityMonitor.java | 70 +++++++ .../crystalwallet/dao/CryptoCurrencyDao.java | 4 + .../fragments/GeneralSettingsFragment.java | 102 ++++++++++ .../fragments/PinSecurityFragment.java | 177 +++++++++++++++++- .../crystalwallet/models/GeneralSetting.java | 1 + .../service/CrystalWalletService.java | 7 +- .../crystalwallet/util/PasswordManager.java | 22 +++ .../validators/PinSecurityValidator.java | 23 +++ .../CurrentPinValidationField.java | 69 +++++++ .../PinConfirmationValidationField.java | 26 ++- .../validationfields/PinValidationField.java | 23 ++- .../views/CryptoCoinBalanceViewHolder.java | 51 ++--- .../main/res/layout/activity_pin_request.xml | 26 +++ .../res/layout/fragment_general_settings.xml | 4 +- .../main/res/layout/fragment_pin_security.xml | 24 ++- 18 files changed, 626 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/util/PasswordManager.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/PinSecurityValidator.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/CurrentPinValidationField.java create mode 100644 app/src/main/res/layout/activity_pin_request.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 52adab6..90f510b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,6 +36,8 @@ + + diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java new file mode 100644 index 0000000..ecc82eb --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java @@ -0,0 +1,42 @@ +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 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.viewmodels.AccountSeedViewModel; + +public class PinRequestActivity extends AppCompatActivity { + + @BindView(R.id.etPassword) + EditText etPassword; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_pin_request); + ButterKnife.bind(this); + } + + @OnTextChanged(value = R.id.etPassword, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterPasswordChanged(Editable editable) { + this.finish(); + } +} + + diff --git a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java index fc5f6c7..64e7345 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java +++ b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java @@ -26,5 +26,7 @@ public class CrystalApplication extends Application { Intent intent = new Intent(getApplicationContext(), CrystalWalletService.class); startService(intent); + + registerActivityLifecycleCallbacks(new CrystalSecurityMonitor()); } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java new file mode 100644 index 0000000..c99729d --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java @@ -0,0 +1,70 @@ +package cy.agorise.crystalwallet.application; + +import android.app.Activity; +import android.app.Application; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Toast; + +import cy.agorise.crystalwallet.activities.CreateSeedActivity; +import cy.agorise.crystalwallet.activities.PinRequestActivity; + +/** + * Created by Henry Varona on 27/1/2018. + */ + +class CrystalSecurityMonitor implements Application.ActivityLifecycleCallbacks { + private int numStarted = 0; + + @Override + public void onActivityStarted(Activity activity) { + if (numStarted == 0) { + callPasswordRequest(activity); + } + numStarted++; + } + + @Override + public void onActivityStopped(Activity activity) { + numStarted--; + if (numStarted == 0) { + callPasswordRequest(activity); + } + } + + public void callPasswordRequest(Activity activity){ + if ((!activity.getIntent().hasExtra("ACTIVITY_TYPE")) || (!activity.getIntent().getStringExtra("ACTIVITY_TYPE").equals("PASSWORD_REQUEST"))) { + //Intent intent = new Intent(activity, PinRequestActivity.class); + //intent.putExtra("ACTIVITY_TYPE", "PASSWORD_REQUEST"); + //activity.startActivity(intent); + } + } + + @Override + public void onActivityCreated(Activity activity, Bundle bundle) { + // + } + + @Override + public void onActivityResumed(Activity activity) { + // + } + + @Override + public void onActivityPaused(Activity activity) { + // + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { + // + } + + @Override + public void onActivityDestroyed(Activity activity) { + // + } + + + +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java index 6826c4f..f821a35 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java @@ -1,5 +1,6 @@ package cy.agorise.crystalwallet.dao; +import android.arch.lifecycle.LiveData; import android.arch.persistence.room.Dao; import android.arch.persistence.room.Insert; import android.arch.persistence.room.OnConflictStrategy; @@ -28,6 +29,9 @@ public interface CryptoCurrencyDao { @Query("SELECT * FROM crypto_currency WHERE id IN (:ids)") List getByIds(List ids); + @Query("SELECT * FROM crypto_currency WHERE name = :name") + LiveData getLiveDataByName(String name); + @Query("SELECT * FROM crypto_currency WHERE name = :name") CryptoCurrency getByName(String name); diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralSettingsFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralSettingsFragment.java index 7be6e28..e91e848 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralSettingsFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralSettingsFragment.java @@ -1,19 +1,44 @@ package cy.agorise.crystalwallet.fragments; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Currency; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +import butterknife.BindView; import butterknife.ButterKnife; +import butterknife.OnItemSelected; import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; /** * Created by xd on 12/28/17. */ public class GeneralSettingsFragment extends Fragment { + + private HashMap countriesMap; + private GeneralSettingListViewModel generalSettingListViewModel; + private LiveData> generalSettingListLiveData; + + @BindView (R.id.spTaxableCountry) + Spinner spTaxableCountry; + public GeneralSettingsFragment() { // Required empty public constructor } @@ -37,6 +62,83 @@ public class GeneralSettingsFragment extends Fragment { View v = inflater.inflate(R.layout.fragment_general_settings, container, false); ButterKnife.bind(this, v); + generalSettingListViewModel = ViewModelProviders.of(this).get(GeneralSettingListViewModel.class); + generalSettingListLiveData = generalSettingListViewModel.getGeneralSettingList(); + + // Initializes the countries spinner + countriesMap = new HashMap(); + String[] countryCodeList = Locale.getISOCountries(); + ArrayList countryAndCurrencyList = new ArrayList(); + String countryAndCurrencyLabel = ""; + for (String countryCode : countryCodeList) { + Locale locale = new Locale("", countryCode); + try { + Currency currency = Currency.getInstance(locale); + countryAndCurrencyLabel = locale.getDisplayCountry() + " (" + currency.getCurrencyCode() + ")"; + countryAndCurrencyList.add(countryAndCurrencyLabel); + countriesMap.put(countryCode, countryAndCurrencyLabel); + countriesMap.put(countryAndCurrencyLabel, countryCode); + } catch (Exception e) { + + } + } + Collections.sort(countryAndCurrencyList); + countryAndCurrencyList.add(0,"SELECT COUNTRY"); + ArrayAdapter countryAdapter = new ArrayAdapter(this.getContext(), android.R.layout.simple_spinner_item, countryAndCurrencyList); + spTaxableCountry.setAdapter(countryAdapter); + + //Observes the general settings data + generalSettingListLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + loadSettings(generalSettings); + } + }); + return v; } + + public GeneralSetting getSetting(String name){ + for (GeneralSetting generalSetting:this.generalSettingListLiveData.getValue()) { + if (generalSetting.getName().equals(name)) { + return generalSetting; + } + } + + return null; + } + + @OnItemSelected(R.id.spTaxableCountry) + void onItemSelected(int position) { + if (position != 0) { + GeneralSetting generalSettingCountryCode = this.getSetting(GeneralSetting.SETTING_NAME_PREFERED_COUNTRY); + GeneralSetting generalSettingCurrency = this.getSetting(GeneralSetting.SETTING_NAME_PREFERED_CURRENCY); + + if (generalSettingCountryCode == null){ + generalSettingCountryCode = new GeneralSetting(); + generalSettingCountryCode.setName(GeneralSetting.SETTING_NAME_PREFERED_COUNTRY); + } + if (generalSettingCurrency == null){ + generalSettingCurrency = new GeneralSetting(); + generalSettingCurrency.setName(GeneralSetting.SETTING_NAME_PREFERED_CURRENCY); + } + + String countryCode = countriesMap.get((String) spTaxableCountry.getSelectedItem()); + Locale locale = new Locale("", countryCode); + Currency currency = Currency.getInstance(locale); + + generalSettingCountryCode.setValue(countryCode); + generalSettingCurrency.setValue(currency.getCurrencyCode()); + this.generalSettingListViewModel.saveGeneralSettings(generalSettingCountryCode, generalSettingCurrency); + } + } + + public void loadSettings(List generalSettings){ + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_NAME_PREFERED_COUNTRY)){ + String preferedCountryCode = generalSetting.getValue(); + spTaxableCountry.setSelection(((ArrayAdapter)spTaxableCountry.getAdapter()).getPosition(countriesMap.get(preferedCountryCode))); + } + } + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/PinSecurityFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/PinSecurityFragment.java index 52e2c55..6c75211 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/PinSecurityFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/PinSecurityFragment.java @@ -1,19 +1,60 @@ package cy.agorise.crystalwallet.fragments; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.text.Editable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; +import java.util.List; + +import butterknife.BindView; import butterknife.ButterKnife; +import butterknife.OnTextChanged; import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.activities.CreateSeedActivity; +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.util.PasswordManager; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; +import cy.agorise.crystalwallet.viewmodels.validators.PinSecurityValidator; +import cy.agorise.crystalwallet.viewmodels.validators.UIValidatorListener; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.ValidationField; /** * Created by xd on 1/18/18. */ -public class PinSecurityFragment extends Fragment { +public class PinSecurityFragment extends Fragment implements UIValidatorListener { + + @BindView(R.id.tvCurrentPin) + TextView tvCurrentPin; + @BindView(R.id.etCurrentPin) + EditText etCurrentPin; + @BindView(R.id.etNewPin) + EditText etNewPin; + @BindView(R.id.etConfirmPin) + EditText etConfirmPin; + + @BindView(R.id.tvCurrentPinError) + TextView tvCurrentPinError; + @BindView(R.id.tvNewPinError) + TextView tvNewPinError; + @BindView(R.id.tvConfirmPinError) + TextView tvConfirmPinError; + + GeneralSettingListViewModel generalSettingListViewModel; + GeneralSetting passwordGeneralSetting; + PinSecurityValidator pinSecurityValidator; public PinSecurityFragment() { // Required empty public constructor @@ -33,6 +74,140 @@ public class PinSecurityFragment extends Fragment { View v = inflater.inflate(R.layout.fragment_pin_security, container, false); ButterKnife.bind(this, v); + generalSettingListViewModel = ViewModelProviders.of(this).get(GeneralSettingListViewModel.class); + LiveData> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList(); + + pinSecurityValidator = new PinSecurityValidator(this.getContext(), etCurrentPin, etNewPin, etConfirmPin); + pinSecurityValidator.setListener(this); + + generalSettingsLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + boolean founded = false; + + if (generalSettings != null){ + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_PASSWORD)){ + founded = true; + if (!generalSetting.getValue().isEmpty()){ + passwordGeneralSetting = generalSetting; + showCurrentPinUI(true); + } else { + showCurrentPinUI(false); + } + break; + } + } + if (!founded){ + showCurrentPinUI(false); + } + } else { + showCurrentPinUI(false); + } + } + }); + return v; } + + public void showCurrentPinUI(Boolean visible){ + if (visible){ + tvCurrentPin.setVisibility(View.VISIBLE); + etCurrentPin.setVisibility(View.VISIBLE); + } else { + tvCurrentPin.setVisibility(View.GONE); + etCurrentPin.setVisibility(View.GONE); + } + } + + @OnTextChanged(value = R.id.etCurrentPin, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterCurrentPinChanged(Editable editable) { + this.pinSecurityValidator.validate(); + } + + @OnTextChanged(value = R.id.etNewPin, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterNewPinChanged(Editable editable) { + this.pinSecurityValidator.validate(); + } + + @OnTextChanged(value = R.id.etConfirmPin, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterConfirmPinChanged(Editable editable) { + this.pinSecurityValidator.validate(); + } + + public void clearFields(){ + if (!this.etCurrentPin.getText().toString().equals("")) { + this.etCurrentPin.setText(""); + } + if (!this.etNewPin.getText().toString().equals("")) { + this.etNewPin.setText(""); + } + if (!this.etConfirmPin.getText().toString().equals("")) { + this.etConfirmPin.setText(""); + } + } + + @Override + public void onValidationSucceeded(final ValidationField field) { + final PinSecurityFragment fragment = this; + + this.getActivity().runOnUiThread(new Runnable() { + public void run() { + + if (field.getView() == etCurrentPin) { + tvCurrentPinError.setText(""); + } else if (field.getView() == etNewPin){ + tvNewPinError.setText(""); + } else if (field.getView() == etConfirmPin){ + tvConfirmPinError.setText(""); + } + + if (pinSecurityValidator.isValid()){ + CharSequence text = "Your password has been sucessfully changed!"; + int duration = Toast.LENGTH_SHORT; + + Toast toast = Toast.makeText(getContext(), text, duration); + toast.show(); + //showCurrentPinUI(true); + + savePassword(etNewPin.getText().toString()); + + + clearFields(); + } + } + }); + } + + public void savePassword(String password) { + String passwordEncripted = PasswordManager.encriptPassword(password); + + if (passwordGeneralSetting == null) { + passwordGeneralSetting = new GeneralSetting(); + passwordGeneralSetting.setName(GeneralSetting.SETTING_PASSWORD); + } + + passwordGeneralSetting.setValue(passwordEncripted); + generalSettingListViewModel.saveGeneralSetting(passwordGeneralSetting); + } + + @Override + public void onValidationFailed(final ValidationField field) { + this.getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + if (field.getView() == etCurrentPin) { + tvCurrentPinError.setText(field.getMessage()); + } else if (field.getView() == etNewPin){ + tvNewPinError.setText(field.getMessage()); + } else if (field.getView() == etConfirmPin){ + tvConfirmPinError.setText(field.getMessage()); + } + } + }); + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/GeneralSetting.java b/app/src/main/java/cy/agorise/crystalwallet/models/GeneralSetting.java index b5b322d..822a10e 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/GeneralSetting.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/GeneralSetting.java @@ -17,6 +17,7 @@ public class GeneralSetting { public final static String SETTING_NAME_PREFERED_COUNTRY = "PREFERED_COUNTRY"; public final static String SETTING_NAME_PREFERED_CURRENCY = "PREFERED_CURRENCY"; + public final static String SETTING_PASSWORD = "PASSWORD"; /** * The id on the database diff --git a/app/src/main/java/cy/agorise/crystalwallet/service/CrystalWalletService.java b/app/src/main/java/cy/agorise/crystalwallet/service/CrystalWalletService.java index 5cecb10..93b89a5 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/service/CrystalWalletService.java +++ b/app/src/main/java/cy/agorise/crystalwallet/service/CrystalWalletService.java @@ -109,8 +109,7 @@ public class CrystalWalletService extends LifecycleService { if (LoadEquivalencesThread != null) { LoadEquivalencesThread.stopLoadingEquivalences(); - } - ; + }; LoadEquivalencesThread = new EquivalencesThread(service, generalSetting.getValue(), bitsharesAssets); LoadEquivalencesThread.start(); } @@ -184,14 +183,14 @@ public class CrystalWalletService extends LifecycleService { } //if (LoadEquivalencesThread == null) { - // LoadEquivalencesThread = new Thread() { + // LoadEquivalencesThread = new EquivalencesThread() { // @Override // public void run() { loadEquivalentsValues(); // } // }; // LoadEquivalencesThread.start(); - //} + // } // If we get killed, after returning from here, restart return START_STICKY; diff --git a/app/src/main/java/cy/agorise/crystalwallet/util/PasswordManager.java b/app/src/main/java/cy/agorise/crystalwallet/util/PasswordManager.java new file mode 100644 index 0000000..7f52970 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/util/PasswordManager.java @@ -0,0 +1,22 @@ +package cy.agorise.crystalwallet.util; + +/** + * Created by Henry Varona on 29/1/2018. + */ + +public class PasswordManager { + + //TODO implement password checking using the encryption implemented in encriptPassword + public static boolean checkPassword(String encriptedPassword, String passwordToCheck){ + if (encriptedPassword.equals(passwordToCheck)){ + return true; + } else { + return false; + } + } + + //TODO implement password encryption + public static String encriptPassword(String password){ + return password; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/PinSecurityValidator.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/PinSecurityValidator.java new file mode 100644 index 0000000..b99f155 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/PinSecurityValidator.java @@ -0,0 +1,23 @@ +package cy.agorise.crystalwallet.viewmodels.validators; + +import android.content.Context; +import android.widget.EditText; + +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.BitsharesAccountNameDoesntExistsValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.CurrentPinValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.PinConfirmationValidationField; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.PinValidationField; + +/** + * Created by Henry Varona on 1/28/2018. + */ + +public class PinSecurityValidator extends UIValidator { + + public PinSecurityValidator(Context context, EditText currentPinEdit, EditText newPinEdit, EditText newPinConfirmationEdit){ + super(context); + this.addField(new CurrentPinValidationField(currentPinEdit)); + this.addField(new PinValidationField(newPinEdit)); + this.addField(new PinConfirmationValidationField(newPinEdit,newPinConfirmationEdit)); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/CurrentPinValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/CurrentPinValidationField.java new file mode 100644 index 0000000..7877184 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/CurrentPinValidationField.java @@ -0,0 +1,69 @@ +package cy.agorise.crystalwallet.viewmodels.validators.validationfields; + +import android.arch.lifecycle.LifecycleOwner; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.widget.EditText; + +import java.util.List; + +import cy.agorise.crystalwallet.models.GeneralSetting; +import cy.agorise.crystalwallet.util.PasswordManager; +import cy.agorise.crystalwallet.viewmodels.GeneralSettingListViewModel; + +/** + * Created by Henry Varona on 1/28/2018. + */ + +public class CurrentPinValidationField extends ValidationField { + + private EditText currentPinField; + String currentPassword = ""; + + public CurrentPinValidationField(EditText currentPinField){ + super(currentPinField); + this.currentPinField = currentPinField; + GeneralSettingListViewModel generalSettingListViewModel = ViewModelProviders.of((FragmentActivity)view.getContext()).get(GeneralSettingListViewModel.class); + LiveData> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList(); + generalSettingsLiveData.observe((LifecycleOwner) this.view.getContext(), new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_PASSWORD)){ + currentPassword = generalSetting.getValue(); + break; + } + } + } + }); + } + + public void validate(){ + final String newValue = currentPinField.getText().toString(); + + if (this.currentPassword.equals("")) { + this.setLastValue(""); + this.startValidating(); + setValidForValue("",true); + } else if (!newValue.equals(this.getLastValue())) { + this.setLastValue(newValue); + this.startValidating(); + if (newValue.equals("")){ + setMessageForValue(lastValue, ""); + setValidForValue(lastValue, false); + } else { + + if (PasswordManager.checkPassword(this.currentPassword, newValue)) { + setValidForValue(lastValue, true); + } else { + setMessageForValue(lastValue, "Password is invalid."); + setValidForValue(lastValue, false); + } + } + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinConfirmationValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinConfirmationValidationField.java index 00f951e..77b7043 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinConfirmationValidationField.java +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinConfirmationValidationField.java @@ -22,18 +22,24 @@ public class PinConfirmationValidationField extends ValidationField { public void validate(){ String newConfirmationValue = pinConfirmationField.getText().toString(); String newValue = pinField.getText().toString(); - String mixedValue = newValue+"_"+newConfirmationValue; - if (mixedValue != this.getLastValue()) { - this.setLastValue(mixedValue); - this.startValidating(); + String mixedValue = newValue + "_" + newConfirmationValue; - - if (!newConfirmationValue.equals(newValue)){ - this.setMessageForValue(mixedValue,this.validator.getContext().getResources().getString(R.string.mismatch_pin)); - this.setValidForValue(mixedValue,false); - } else { - this.setValidForValue(mixedValue, true); + if (!newConfirmationValue.equals("")) { + if (!mixedValue.equals(this.getLastValue())) { + this.setLastValue(mixedValue); + this.startValidating(); + if (!newConfirmationValue.equals(newValue)) { + this.setMessageForValue(mixedValue, this.validator.getContext().getResources().getString(R.string.mismatch_pin)); + this.setValidForValue(mixedValue, false); + } else { + this.setValidForValue(mixedValue, true); + } } + } else { + this.setLastValue(""); + this.startValidating(); + this.setMessageForValue("", ""); + this.setValidForValue("", false); } } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinValidationField.java index 809b8c6..6228641 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinValidationField.java +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/PinValidationField.java @@ -20,16 +20,23 @@ public class PinValidationField extends ValidationField { public void validate(){ String newValue = pinField.getText().toString(); - if (newValue != this.getLastValue()) { - this.setLastValue(newValue); - this.startValidating(); + if (!newValue.equals("")) { + if (!newValue.equals(this.getLastValue())) { + this.setLastValue(newValue); + this.startValidating(); - if (newValue.length() < 6) { - this.setMessageForValue(newValue, this.validator.getContext().getResources().getString(R.string.pin_number_warning)); - this.setValidForValue(newValue, false); - } else { - this.setValidForValue(newValue, true); + if (newValue.length() < 6) { + this.setMessageForValue(newValue, this.validator.getContext().getResources().getString(R.string.pin_number_warning)); + this.setValidForValue(newValue, false); + } else { + this.setValidForValue(newValue, true); + } } + } else { + this.setLastValue(""); + this.startValidating(); + this.setMessageForValue("", ""); + this.setValidForValue("", false); } } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java index a17cb05..45e0168 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java @@ -80,30 +80,39 @@ public class CryptoCoinBalanceViewHolder extends RecyclerView.ViewHolder { public void onChanged(@Nullable GeneralSetting generalSetting) { if (generalSetting != null) { //Gets the currency object of the preferred currency - CryptoCurrency currencyTo = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getByName(generalSetting.getValue()); + LiveData currencyToLiveData = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getLiveDataByName(generalSetting.getValue()); - //Retrieves the equivalent value of this balance using the "From" currency and the "To" currency - LiveData currencyEquivalenceLiveData = CrystalDatabase.getAppDatabase(context) - .cryptoCurrencyEquivalenceDao().getByFromTo( - currencyTo.getId(), - currencyFrom.getId() - - ); - - //Observes the equivalent value. If the equivalent value changes, this will keep the value showed correct - currencyEquivalenceLiveData.observe((LifecycleOwner) context, new Observer() { + currencyToLiveData.observe((LifecycleOwner) context, new Observer() { @Override - public void onChanged(@Nullable CryptoCurrencyEquivalence cryptoCurrencyEquivalence) { - if (cryptoCurrencyEquivalence != null) { - CryptoCurrency toCurrency = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getById(cryptoCurrencyEquivalence.getFromCurrencyId()); - String equivalenceString = String.format( - "%.2f", - (balance.getBalance()/Math.pow(10,currencyFrom.getPrecision()))/ - (cryptoCurrencyEquivalence.getValue()/Math.pow(10,toCurrency.getPrecision())) - ); + public void onChanged(@Nullable CryptoCurrency cryptoCurrency) { + if (cryptoCurrency != null) { + CryptoCurrency currencyTo = cryptoCurrency; - cryptoCoinBalanceEquivalence.setText( - equivalenceString + " " + toCurrency.getName()); + //Retrieves the equivalent value of this balance using the "From" currency and the "To" currency + LiveData currencyEquivalenceLiveData = CrystalDatabase.getAppDatabase(context) + .cryptoCurrencyEquivalenceDao().getByFromTo( + currencyTo.getId(), + currencyFrom.getId() + + ); + + //Observes the equivalent value. If the equivalent value changes, this will keep the value showed correct + currencyEquivalenceLiveData.observe((LifecycleOwner) context, new Observer() { + @Override + public void onChanged(@Nullable CryptoCurrencyEquivalence cryptoCurrencyEquivalence) { + if (cryptoCurrencyEquivalence != null) { + CryptoCurrency toCurrency = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getById(cryptoCurrencyEquivalence.getFromCurrencyId()); + String equivalenceString = String.format( + "%.2f", + (balance.getBalance() / Math.pow(10, currencyFrom.getPrecision())) / + (cryptoCurrencyEquivalence.getValue() / Math.pow(10, toCurrency.getPrecision())) + ); + + cryptoCoinBalanceEquivalence.setText( + equivalenceString + " " + toCurrency.getName()); + } + } + }); } } }); diff --git a/app/src/main/res/layout/activity_pin_request.xml b/app/src/main/res/layout/activity_pin_request.xml new file mode 100644 index 0000000..58fa56f --- /dev/null +++ b/app/src/main/res/layout/activity_pin_request.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_general_settings.xml b/app/src/main/res/layout/fragment_general_settings.xml index 6364bb0..257d0f2 100644 --- a/app/src/main/res/layout/fragment_general_settings.xml +++ b/app/src/main/res/layout/fragment_general_settings.xml @@ -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" /> + + + app:layout_constraintTop_toBottomOf="@+id/tvCurrentPinError" /> + - + app:layout_constraintTop_toBottomOf="@+id/tvNewPinError" /> + \ No newline at end of file From cb309f1db22051c02361e0e63319bf7b23ddb88b Mon Sep 17 00:00:00 2001 From: Javier Varona Date: Wed, 31 Jan 2018 20:42:19 -0400 Subject: [PATCH 04/17] - Pin lock screen is working --- .../activities/IntroActivity.java | 25 ++------- .../activities/PinRequestActivity.java | 34 +++++++++++- .../application/CrystalApplication.java | 2 - .../application/CrystalSecurityMonitor.java | 55 ++++++++++++++++--- .../main/res/layout/activity_pin_request.xml | 4 +- 5 files changed, 87 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java index f638df3..ad10adb 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java @@ -1,36 +1,16 @@ 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.os.Bundle; -import android.support.annotation.Nullable; import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.Button; - -import java.util.List; import cy.agorise.crystalwallet.R; -import cy.agorise.crystalwallet.dao.CrystalDatabase; -import cy.agorise.crystalwallet.models.AccountSeed; -import cy.agorise.crystalwallet.models.CryptoCoinBalance; -import cy.agorise.crystalwallet.models.CryptoCoinTransaction; -import cy.agorise.crystalwallet.models.CryptoNetAccount; -import cy.agorise.crystalwallet.randomdatagenerators.RandomCryptoCoinBalanceGenerator; -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; @@ -41,6 +21,9 @@ public class IntroActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_intro); + this.getApplication().registerActivityLifecycleCallbacks(new CrystalSecurityMonitor(this)); + + //Checks if the user has any seed created AccountSeedListViewModel accountSeedListViewModel = ViewModelProviders.of(this).get(AccountSeedListViewModel.class); diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java index ecc82eb..a95210a 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/PinRequestActivity.java @@ -11,6 +11,8 @@ 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; @@ -18,9 +20,17 @@ 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; @@ -30,12 +40,34 @@ public class PinRequestActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_pin_request); ButterKnife.bind(this); + + GeneralSettingListViewModel generalSettingListViewModel = ViewModelProviders.of(this).get(GeneralSettingListViewModel.class); + LiveData> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList(); + generalSettingsLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + passwordEncrypted = ""; + + if (generalSettings != null){ + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_PASSWORD)){ + if (!generalSetting.getValue().isEmpty()){ + passwordEncrypted = generalSetting.getValue(); + } + break; + } + } + } + } + }); } @OnTextChanged(value = R.id.etPassword, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) void afterPasswordChanged(Editable editable) { - this.finish(); + if (PasswordManager.checkPassword(passwordEncrypted, etPassword.getText().toString())) { + this.finish(); + } } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java index 64e7345..fc5f6c7 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java +++ b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalApplication.java @@ -26,7 +26,5 @@ public class CrystalApplication extends Application { Intent intent = new Intent(getApplicationContext(), CrystalWalletService.class); startService(intent); - - registerActivityLifecycleCallbacks(new CrystalSecurityMonitor()); } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java index c99729d..852daa9 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java +++ b/app/src/main/java/cy/agorise/crystalwallet/application/CrystalSecurityMonitor.java @@ -1,25 +1,62 @@ 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.widget.Toast; +import android.support.annotation.Nullable; +import android.support.v4.app.FragmentActivity; + +import java.util.List; -import cy.agorise.crystalwallet.activities.CreateSeedActivity; 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. */ -class CrystalSecurityMonitor implements Application.ActivityLifecycleCallbacks { +public class CrystalSecurityMonitor implements Application.ActivityLifecycleCallbacks { private int numStarted = 0; + private String passwordEncrypted; + + public CrystalSecurityMonitor(final FragmentActivity fragmentActivity){ + GeneralSettingListViewModel generalSettingListViewModel = ViewModelProviders.of(fragmentActivity).get(GeneralSettingListViewModel.class); + LiveData> generalSettingsLiveData = generalSettingListViewModel.getGeneralSettingList(); + + generalSettingsLiveData.observe(fragmentActivity, new Observer>() { + @Override + public void onChanged(@Nullable List generalSettings) { + boolean founded = false; + passwordEncrypted = ""; + + if (generalSettings != null){ + for (GeneralSetting generalSetting:generalSettings) { + if (generalSetting.getName().equals(GeneralSetting.SETTING_PASSWORD)){ + founded = true; + if (!generalSetting.getValue().isEmpty()){ + passwordEncrypted = generalSetting.getValue(); + callPasswordRequest(fragmentActivity); + } + break; + } + } + } + } + }); + } @Override public void onActivityStarted(Activity activity) { if (numStarted == 0) { - callPasswordRequest(activity); + if ((this.passwordEncrypted != null) && (!this.passwordEncrypted.equals(""))) { + callPasswordRequest(activity); + } } numStarted++; } @@ -28,15 +65,17 @@ class CrystalSecurityMonitor implements Application.ActivityLifecycleCallbacks { public void onActivityStopped(Activity activity) { numStarted--; if (numStarted == 0) { - callPasswordRequest(activity); + 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); + Intent intent = new Intent(activity, PinRequestActivity.class); + intent.putExtra("ACTIVITY_TYPE", "PASSWORD_REQUEST"); + activity.startActivity(intent); } } diff --git a/app/src/main/res/layout/activity_pin_request.xml b/app/src/main/res/layout/activity_pin_request.xml index 58fa56f..d05df7d 100644 --- a/app/src/main/res/layout/activity_pin_request.xml +++ b/app/src/main/res/layout/activity_pin_request.xml @@ -18,9 +18,11 @@ android:padding="10dp" android:background="@color/colorPrimary" android:text="Enter Pin:"/> + + android:layout_height="wrap_content" + android:inputType="numberPassword" /> \ No newline at end of file From 5eae79d011cfa586dcca87553251c04a3df71f3c Mon Sep 17 00:00:00 2001 From: Javier Varona Date: Sun, 4 Feb 2018 21:43:20 -0400 Subject: [PATCH 05/17] - The user can add contacts (Only the name. Still in progress...) --- app/src/main/AndroidManifest.xml | 52 ++++---- .../activities/BoardActivity.java | 6 + .../activities/CreateContactActivity.java | 113 ++++++++++++++++ .../agorise/crystalwallet/dao/ContactDao.java | 6 + .../fragments/ContactsFragment.java | 29 +++- .../viewmodels/ContactListViewModel.java | 4 + .../viewmodels/ContactViewModel.java | 27 ++++ .../validators/CreateContactValidator.java | 21 +++ .../ContactNameValidationField.java | 59 +++++++++ .../crystalwallet/views/ContactListView.java | 2 - .../res/layout/activity_create_contact.xml | 124 ++++++++++++++++++ 11 files changed, 413 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/cy/agorise/crystalwallet/activities/CreateContactActivity.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactViewModel.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/CreateContactValidator.java create mode 100644 app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/ContactNameValidationField.java create mode 100644 app/src/main/res/layout/activity_create_contact.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 90f510b..cfdfda8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,11 +1,12 @@ + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + - + + + \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java index 9ef1161..e113438 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java @@ -204,6 +204,12 @@ public class BoardActivity extends AppCompatActivity { } } + @OnClick(R.id.fabAddContact) + public void beginCreateContact(){ + Intent intent = new Intent(this, CreateContactActivity.class); + startActivity(intent); + } + /* * dispatch the user to the receive fragment using this account */ diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/CreateContactActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/CreateContactActivity.java new file mode 100644 index 0000000..ae5aa5f --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/CreateContactActivity.java @@ -0,0 +1,113 @@ +package cy.agorise.crystalwallet.activities; + +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.text.Editable; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +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.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.UIValidatorListener; +import cy.agorise.crystalwallet.viewmodels.validators.validationfields.ValidationField; + +public class CreateContactActivity extends AppCompatActivity implements UIValidatorListener { + + @BindView(R.id.etName) + EditText etName; + @BindView(R.id.tvNameError) + TextView tvNameError; + @BindView(R.id.btnCancel) + Button btnCancel; + @BindView(R.id.btnCreate) + Button btnCreate; + + CreateContactValidator createContactValidator; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_create_contact); + ButterKnife.bind(this); + + btnCreate.setEnabled(false); + createContactValidator = new CreateContactValidator(this.getApplicationContext(),etName); + createContactValidator.setListener(this); + } + + @OnTextChanged(value = R.id.etName, + callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) + void afterContactNameChanged(Editable editable) { + this.createContactValidator.validate(); + } + + + @OnClick(R.id.btnCancel) + public void cancel(){ + this.finish(); + } + + @OnClick(R.id.btnCreate) + public void createContact(){ + if (this.createContactValidator.isValid()) { + Contact newContact = new Contact(); + newContact.setName(etName.getText().toString()); + 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(""); + } + + if (activity.createContactValidator.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()); + } + } + }); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java index 77dd3fd..13425ea 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/ContactDao.java @@ -26,4 +26,10 @@ public interface ContactDao { @Query("SELECT * FROM contact WHERE id = :id") LiveData getById(long id); + + @Query("SELECT count(*) FROM contact WHERE name = :name") + boolean existsByName(String name); + + @Insert(onConflict = OnConflictStrategy.ABORT) + public long[] add(Contact... contacts); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactsFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactsFragment.java index 558d81b..34b2652 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactsFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/ContactsFragment.java @@ -1,14 +1,28 @@ package cy.agorise.crystalwallet.fragments; +import android.arch.lifecycle.LiveData; +import android.arch.lifecycle.Observer; +import android.arch.lifecycle.ViewModelProviders; +import android.arch.paging.PagedList; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import butterknife.BindView; +import butterknife.ButterKnife; import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.models.Contact; +import cy.agorise.crystalwallet.viewmodels.ContactListViewModel; +import cy.agorise.crystalwallet.views.ContactListView; public class ContactsFragment extends Fragment { + + @BindView(R.id.vContactListView) + ContactListView contactListView; + public ContactsFragment() { // Required empty public constructor } @@ -29,6 +43,19 @@ 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); + LiveData> contactsLiveData = contactListViewModel.getContactList(); + + contactsLiveData.observe(this, new Observer>() { + @Override + public void onChanged(@Nullable PagedList contacts) { + contactListView.setData(contacts); + } + }); + + return v; } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java index ec076d9..4bd2522 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactListViewModel.java @@ -33,4 +33,8 @@ public class ContactListViewModel extends AndroidViewModel { public LiveData> getContactList(){ return this.contactList; } + + public boolean contactExists(String name){ + return this.db.contactDao().existsByName(name); + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactViewModel.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactViewModel.java new file mode 100644 index 0000000..1ac999e --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/ContactViewModel.java @@ -0,0 +1,27 @@ +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 cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.models.Contact; + +/** + * Created by Henry Varona on 2/4/2018. + */ + +public class ContactViewModel extends AndroidViewModel { + + private CrystalDatabase db; + + public ContactViewModel(Application application) { + super(application); + this.db = CrystalDatabase.getAppDatabase(application.getApplicationContext()); + } + + public boolean addContact(Contact contact){ + return this.db.contactDao().add(contact)[0] >= 0; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/CreateContactValidator.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/CreateContactValidator.java new file mode 100644 index 0000000..34e43ae --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/CreateContactValidator.java @@ -0,0 +1,21 @@ +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.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){ + super(context); + this.addField(new ContactNameValidationField(nameEdit)); + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/ContactNameValidationField.java b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/ContactNameValidationField.java new file mode 100644 index 0000000..cdca074 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/viewmodels/validators/validationfields/ContactNameValidationField.java @@ -0,0 +1,59 @@ +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.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; + + public ContactNameValidationField(EditText nameField){ + super(nameField); + this.nameField = nameField; + } + + public void validate(){ + final String newValue = this.nameField.getText().toString(); + + + if (!newValue.equals("")) { + if (!newValue.equals(this.getLastValue())) { + this.setLastValue(newValue); + this.startValidating(); + + ContactListViewModel contactListViewModel = ViewModelProviders.of((FragmentActivity) view.getContext()).get(ContactListViewModel.class); + if (contactListViewModel.contactExists(newValue)) { + this.setMessageForValue(newValue, "This name is already used by another contact."); + this.setValidForValue(newValue, false); + } else { + this.setValidForValue(newValue, true); + } + } + } else { + this.setLastValue(""); + this.startValidating(); + this.setMessageForValue("", ""); + this.setValidForValue("", false); + } + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java b/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java index e9efce9..854be6b 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/ContactListView.java @@ -12,9 +12,7 @@ import android.widget.RelativeLayout; import cy.agorise.crystalwallet.R; import cy.agorise.crystalwallet.models.Contact; -import cy.agorise.crystalwallet.models.CryptoCoinTransaction; import cy.agorise.crystalwallet.viewmodels.ContactListViewModel; -import cy.agorise.crystalwallet.viewmodels.TransactionListViewModel; /** * Created by Henry Varona on 1/15/2018. diff --git a/app/src/main/res/layout/activity_create_contact.xml b/app/src/main/res/layout/activity_create_contact.xml new file mode 100644 index 0000000..7576a93 --- /dev/null +++ b/app/src/main/res/layout/activity_create_contact.xml @@ -0,0 +1,124 @@ + + + + + + + + + + +