diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java new file mode 100644 index 0000000..1620297 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/BitsharesFaucetApiGenerator.java @@ -0,0 +1,8 @@ +package cy.agorise.crystalwallet.apigenerator; + +/** + * Created by henry on 15/10/2017. + */ + +public class BitsharesFaucetApiGenerator { +} 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 812abe1..e955041 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/GrapheneApiGenerator.java @@ -1,16 +1,24 @@ package cy.agorise.crystalwallet.apigenerator; +import android.arch.lifecycle.LiveData; +import android.content.Context; + import java.io.Serializable; import java.util.ArrayList; import java.util.List; +import cy.agorise.crystalwallet.dao.CrystalDatabase; import cy.agorise.crystalwallet.models.BitsharesAsset; +import cy.agorise.crystalwallet.models.CryptoCoinBalance; +import cy.agorise.crystalwallet.models.CryptoNetBalance; import cy.agorise.crystalwallet.network.WebSocketThread; import cy.agorise.graphenej.Address; import cy.agorise.graphenej.Asset; +import cy.agorise.graphenej.AssetAmount; import cy.agorise.graphenej.ObjectType; import cy.agorise.graphenej.Transaction; import cy.agorise.graphenej.UserAccount; +import cy.agorise.graphenej.api.GetAccountBalances; import cy.agorise.graphenej.api.GetAccountByName; import cy.agorise.graphenej.api.GetAccounts; import cy.agorise.graphenej.api.GetKeyReferences; @@ -36,7 +44,7 @@ import cy.agorise.graphenej.models.WitnessResponse; * Created by henry on 26/9/2017. */ -public class GrapheneApiGenerator { +public abstract class GrapheneApiGenerator { //TODO network connections //TODO make to work with all Graphene stype, not only bitshares @@ -114,18 +122,18 @@ public class GrapheneApiGenerator { /** * Gets the Transaction for an Account * - * @param accountId The account id to search + * @param accountGrapheneId The account id to search * @param start The start index of the transaction list * @param stop The stop index of the transaction list * @param limit the maximun transactions to retrieve * @param request The Api request object, to answer this petition */ - public static void getAccountTransaction(String accountId, int start, int stop, int limit, final ApiRequest request){ - WebSocketThread thread = new WebSocketThread(new GetRelativeAccountHistory(new UserAccount(accountId), stop, limit, start, new WitnessResponseListener() { + public static void getAccountTransaction(String accountGrapheneId, int start, int stop, int limit, final ApiRequest request){ + WebSocketThread thread = new WebSocketThread(new GetRelativeAccountHistory(new UserAccount(accountGrapheneId), stop, limit, start, new WitnessResponseListener() { @Override public void onSuccess(WitnessResponse response) { WitnessResponse> resp = response; - request.getListener().success(resp,request.getId()); + request.getListener().success(resp.result,request.getId()); } @Override @@ -136,6 +144,32 @@ public class GrapheneApiGenerator { thread.start(); } + /** + * Retrieves the account id by the name of the account + * + * @param accountName The account Name to find + * @param request The Api request object, to answer this petition + */ + public static void getAccountByName(String accountName, final ApiRequest request){ + WebSocketThread thread = new WebSocketThread(new GetAccountByName(accountName, new WitnessResponseListener() { + @Override + public void onSuccess(WitnessResponse response) { + AccountProperties accountProperties = ((WitnessResponse) response).result; + if(accountProperties == null){ + request.getListener().fail(request.getId()); + }else{ + request.getListener().success(accountProperties,request.getId()); + } + } + + @Override + public void onError(BaseResponse.Error error) { + request.getListener().fail(request.getId()); + } + }),url); + thread.start(); + } + /** * Retrieves the account id by the name of the account * @@ -221,14 +255,12 @@ public class GrapheneApiGenerator { } public static void getAssetById(ArrayList assetIds, final ApiRequest request){ - //TODO the graphenej library needs a way to creates the Asset object only with the symbol ArrayList assets = new ArrayList<>(); for(String assetId : assetIds){ Asset asset = new Asset(assetId); assets.add(asset); } - //TODO the graphenj library needs to add the lookupAssetSymbols to be able to search the asset by symbol - // this can be done with the same lookupassetsymbol, but passing only the symbol not the objetcid + WebSocketThread thread = new WebSocketThread(new LookupAssetSymbols(assets, new WitnessResponseListener() { @Override public void onSuccess(WitnessResponse response) { @@ -263,7 +295,8 @@ public class GrapheneApiGenerator { thread.start(); } - public static void subscribeBitsharesAccount(final String accountId){ + public static void subscribeBitsharesAccount(long accountId, final String accountBitsharesId, Context context){ + final LiveData> balances = CrystalDatabase.getAppDatabase(context).cryptoCoinBalanceDao().getBalancesFromAccount(accountId); SubscriptionListener balanceListener = new SubscriptionListener() { @Override public ObjectType getInterestObjectType() { @@ -277,7 +310,10 @@ public class GrapheneApiGenerator { for(Serializable update : updatedObjects){ if(update instanceof AccountBalanceUpdate){ AccountBalanceUpdate balanceUpdate = (AccountBalanceUpdate) update; - if(balanceUpdate.owner.equals(accountId)){ + if(balanceUpdate.owner.equals(accountBitsharesId)){ + boolean find = false; + for(CryptoCoinBalance balance : balances.getValue()){ + } //TODO balance function //TODO refresh transactions } @@ -300,4 +336,31 @@ public class GrapheneApiGenerator { bitsharesSubscriptionHub.cancelSubscriptions(); } + public static void getAccountBalance(final long accountId, final String accountGrapheneId, final Context context){ + + + WebSocketThread thread = new WebSocketThread(new GetAccountBalances(new UserAccount(accountGrapheneId), null, new WitnessResponseListener() { + @Override + public void onSuccess(WitnessResponse response) { + List balances = (List) response.result; + for(AssetAmount balance : balances){ + CryptoCoinBalance ccBalance = new CryptoCoinBalance(); + ccBalance.setAccountId(accountId); + ccBalance.setBalance(balance.getAmount().longValue()); + + //TODO cryptocyrrency + CrystalDatabase.getAppDatabase(context).cryptoCoinBalanceDao().insertCryptoCoinBalance(ccBalance); + } + } + + @Override + public void onError(BaseResponse.Error error) { + + } + }),url); + + thread.start(); + + } + } diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java new file mode 100644 index 0000000..30f09a7 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/CryptoCurrencyDao.java @@ -0,0 +1,28 @@ +package cy.agorise.crystalwallet.dao; + +import android.arch.persistence.room.Dao; +import android.arch.persistence.room.Insert; +import android.arch.persistence.room.OnConflictStrategy; +import android.arch.persistence.room.Query; + +import java.util.List; + +import cy.agorise.crystalwallet.models.CryptoCurrency; + +/** + * Created by henry on 15/10/2017. + */ + +@Dao +public interface CryptoCurrencyDao { + + @Query("SELECT * FROM crypto_currency") + List getAll(); + + @Query("SELECT * FROM crypto_currency WHERE id := id") + CryptoCurrency getById(int id); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + public long[] insertCryptoCurrency(CryptoCurrency... currencies); + +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java b/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java index 07c8304..da2e73c 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java @@ -30,6 +30,7 @@ public abstract class CrystalDatabase extends RoomDatabase { public abstract CryptoNetAccountDao cryptoNetAccountDao(); public abstract TransactionDao transactionDao(); public abstract CryptoCoinBalanceDao cryptoCoinBalanceDao(); + public abstract CryptoCurrencyDao cryptoCurrencyDao(); public static CrystalDatabase getAppDatabase(Context context) { if (instance == null) { 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 2149ec3..bbc8d39 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/TransactionDao.java @@ -23,6 +23,9 @@ public interface TransactionDao { @Query("SELECT * FROM crypto_coin_transaction ORDER BY date ASC") LivePagedListProvider transactionsByDate(); + @Query("SELECT * FROM crypto_coin_transaction WHERE account_id := idAccount ORDER BY date ASC") + LiveData> getByIdAccount(long idAccount); + @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 51a5f12..c8f5dea 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java @@ -1,12 +1,15 @@ package cy.agorise.crystalwallet.manager; import android.arch.lifecycle.LiveData; +import android.content.Context; import com.google.common.primitives.UnsignedLong; import org.bitcoinj.core.ECKey; import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import cy.agorise.crystalwallet.apigenerator.ApiRequest; import cy.agorise.crystalwallet.apigenerator.ApiRequestListener; @@ -18,6 +21,9 @@ import cy.agorise.crystalwallet.cryptonetinforequests.ValidateExistBitsharesAcco import cy.agorise.crystalwallet.cryptonetinforequests.ValidateImportBitsharesAccountRequest; import cy.agorise.crystalwallet.dao.CrystalDatabase; import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.models.BitsharesAsset; +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.graphenej.Address; @@ -29,6 +35,7 @@ import cy.agorise.graphenej.PublicKey; import cy.agorise.graphenej.Transaction; import cy.agorise.graphenej.UserAccount; import cy.agorise.graphenej.models.AccountProperties; +import cy.agorise.graphenej.models.HistoricalTransfer; import cy.agorise.graphenej.operations.TransferOperationBuilder; /** @@ -37,37 +44,52 @@ import cy.agorise.graphenej.operations.TransferOperationBuilder; public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetInfoRequestsListener { @Override - public CryptoNetAccount createAccountFromSeed(CryptoNetAccount account) { + public CryptoNetAccount createAccountFromSeed(CryptoNetAccount account, Context context) { //TODO generate account keys - //TODO register account + //TODO register account faucet api //TODO save account on DB //TODO subscribe account return null; } @Override - public CryptoNetAccount importAccountFromSeed(CryptoNetAccount account) { - //TODO check account info - //TODO save account on DB - //TODO subscribe account - //TODO refresh account transaction - //TODO refresh account balance + public CryptoNetAccount importAccountFromSeed(CryptoNetAccount account, Context context) { + + if(account instanceof GrapheneAccount) { + GrapheneAccount grapheneAccount = (GrapheneAccount) account; + + if(grapheneAccount.getAccountId() == null){ + grapheneAccount = this.getAccountInfoByName(grapheneAccount.getName()); + }else if(grapheneAccount.getName() == null){ + grapheneAccount = this.getAccountInfoById(grapheneAccount.getAccountId()); + } + //TODO grapaheneAccount null, error fetching + + CrystalDatabase db = CrystalDatabase.getAppDatabase(context); + //TODO save account on DB + + GrapheneApiGenerator.subscribeBitsharesAccount(grapheneAccount.getId(), grapheneAccount.getAccountId(), context); + this.refreshAccountTransactions(account.getId(), context); + GrapheneApiGenerator.getAccountBalance(grapheneAccount.getId(), grapheneAccount.getAccountId(), context); + return grapheneAccount; + } return null; } @Override - public void loadAccountFromDB(CryptoNetAccount account) { + public void loadAccountFromDB(CryptoNetAccount account, Context context) { if(account instanceof GrapheneAccount){ GrapheneAccount grapheneAccount = (GrapheneAccount) account; if(grapheneAccount.getAccountId() == null){ - //TODO find account data by name + grapheneAccount = this.getAccountInfoByName(grapheneAccount.getName()); }else if(grapheneAccount.getName() == null){ - //TODO find account data by id + grapheneAccount = this.getAccountInfoById(grapheneAccount.getAccountId()); } + //TODO grapaheneAccount null, error fetching - GrapheneApiGenerator.subscribeBitsharesAccount(grapheneAccount.getAccountId()); - //TODO refresh account transactions - //TODO refresh account balance + GrapheneApiGenerator.subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context); + this.refreshAccountTransactions(account.getId(),context); + GrapheneApiGenerator.getAccountBalance(grapheneAccount.getId(),grapheneAccount.getAccountId(),context); } } @@ -77,6 +99,8 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI this.validateImportAccount((ValidateImportBitsharesAccountRequest) request); } else if (request instanceof ValidateExistBitsharesAccountRequest){ this.validateExistAcccount((ValidateExistBitsharesAccountRequest) request); + } else if (request instanceof ValidateBitsharesSendRequest){ + this.validateSendRequest((ValidateBitsharesSendRequest) request); } } @@ -168,4 +192,162 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI GrapheneApiGenerator.broadcastTransaction(transaction,feeAsset, transactionRequest); } + + private GrapheneAccount getAccountInfoById(String grapheneId){ + final Object SYNC = new Object(); + long timeout = 60000; + + AccountIdOrNameListener listener = new AccountIdOrNameListener(SYNC); + + 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 e) {} + } + } + + return listener.account; + } + + private GrapheneAccount getAccountInfoByName(String grapheneName){ + final Object SYNC = new Object(); + long timeout = 60000; + + AccountIdOrNameListener listener = new AccountIdOrNameListener(SYNC); + + ApiRequest request = new ApiRequest(0, listener); + GrapheneApiGenerator.getAccountByName(grapheneName,request); + + long cTime = System.currentTimeMillis(); + + while(!listener.ready && (System.currentTimeMillis()-cTime) < timeout){ + synchronized (SYNC){ + try { + SYNC.wait(100); + } catch (InterruptedException e) {} + } + } + + return listener.account; + } + + public void refreshAccountTransactions(final long idAccount, Context context){ + final CrystalDatabase db = CrystalDatabase.getAppDatabase(context); + final LiveData> transactions = db.transactionDao().getByIdAccount(idAccount); + final LiveData account = null; + //TODO find account + int start = transactions.getValue().size(); + int limit = 50; + int stop = start + limit; + + ApiRequest transactionRequest = new ApiRequest(0, new TransactionRequestListener(start,stop,limit,account.getValue(),db)); + + + GrapheneApiGenerator.getAccountTransaction(account.getValue().getName(),start,stop,limit,transactionRequest); + } + + private class TransactionRequestListener implements ApiRequestListener{ + + int start; + int stop; + int limit; + GrapheneAccount account; + CrystalDatabase db; + + public TransactionRequestListener(int start, int stop, int limit, GrapheneAccount account, CrystalDatabase db) { + this.start = start; + this.stop = stop; + this.limit = limit; + this.account = account; + this.db = db; + } + + @Override + public void success(Object answer, int idPetition) { + List transfers = (List) answer ; + for(HistoricalTransfer transfer : transfers){ + CryptoCoinTransaction transaction = new CryptoCoinTransaction(); + transaction.setAccountId(account.getId()); + transaction.setAmount(transfer.getOperation().getAssetAmount().getAmount().longValue()); + BitsharesAsset currency = null; + //TODO find currency by symbol db.cryptoCurrencyDao().getBySymbol(transfer.getOperation().getAssetAmount().getAsset().getSymbol()) + if(currency == null){ + //CryptoCurrency not in database + Asset asset = transfer.getOperation().getAssetAmount().getAsset(); + BitsharesAsset.Type assetType = null; + if(asset.getAssetType() == Asset.AssetType.CORE_ASSET){ + assetType = BitsharesAsset.Type.CORE; + }else if(asset.getAssetType() == Asset.AssetType.SMART_COIN){ + assetType = BitsharesAsset.Type.SMART_COIN; + }else if(asset.getAssetType() == Asset.AssetType.PREDICTION_MARKET){ + assetType = BitsharesAsset.Type.PREDICTION_MARKET; + }else if(asset.getAssetType() == Asset.AssetType.UIA){ + assetType = BitsharesAsset.Type.UIA; + } + currency = new BitsharesAsset(asset.getSymbol(),asset.getPrecision(),asset.getObjectId(), assetType); + db.cryptoCurrencyDao().insertCryptoCurrency(currency); + + } + transaction.setIdCurrency(currency.getId()); + transaction.setConfirmed(true); //graphene transaction are always confirmed + //TODO date of the transaction transaction.setDate(transfer.); + transaction.setFrom(transfer.getOperation().getFrom().getName()); + transaction.setInput(!transfer.getOperation().getFrom().getName().equals(account.getName())); + transaction.setTo(transfer.getOperation().getTo().getName()); + db.transactionDao().insertTransaction(transaction); + } + if(transfers.size()>= limit){ + int newStart= start + limit; + int newStop= stop + limit; + ApiRequest transactionRequest = new ApiRequest(newStart/limit, new TransactionRequestListener(newStart,newStop,limit,account,db)); + GrapheneApiGenerator.getAccountTransaction(account.getName(),newStart,newStop,limit,transactionRequest); + } + } + + @Override + public void fail(int idPetition) { + + } + } + + private class AccountIdOrNameListener implements ApiRequestListener{ + Object SYNC; + boolean ready = false; + + GrapheneAccount account; + + public AccountIdOrNameListener(Object SYNC) { + this.SYNC = SYNC; + } + + @Override + public void success(Object answer, int idPetition) { + if(answer instanceof AccountProperties){ + AccountProperties props = (AccountProperties) answer; + account = new GrapheneAccount(); + account.setAccountId(props.id); + account.setName(props.name); + } + + synchronized (SYNC){ + ready = true; + SYNC.notifyAll(); + } + } + + @Override + public void fail(int idPetition) { + synchronized (SYNC){ + ready = true; + SYNC.notifyAll(); + } + } + } + } 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 709a1ba..42c9a3d 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/CryptoAccountManager.java @@ -1,5 +1,7 @@ package cy.agorise.crystalwallet.manager; +import android.content.Context; + import cy.agorise.crystalwallet.models.AccountSeed; import cy.agorise.crystalwallet.models.CryptoNetAccount; @@ -16,19 +18,19 @@ 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); + public CryptoNetAccount createAccountFromSeed(CryptoNetAccount account, 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); + public CryptoNetAccount importAccountFromSeed(CryptoNetAccount account, Context context); /** * Loads account data from the database * * @param account The CryptoNetAccount to be loaded */ - public void loadAccountFromDB(CryptoNetAccount account); + public void loadAccountFromDB(CryptoNetAccount account, Context context); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinBalance.java b/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinBalance.java index 92583ea..a5e1844 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinBalance.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinBalance.java @@ -38,7 +38,7 @@ public class CryptoCoinBalance { private CryptoCoin mCoin; @ColumnInfo(name = "balance") - private int mBalance; + private long mBalance; public long getId() { return mId; @@ -64,11 +64,11 @@ public class CryptoCoinBalance { this.mCoin = coin; } - public int getBalance() { + public long getBalance() { return mBalance; } - public void setBalance(int balance) { + public void setBalance(long balance) { this.mBalance = balance; } diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinTransaction.java b/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinTransaction.java index ec4e2e2..e6dff18 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinTransaction.java +++ b/app/src/main/java/cy/agorise/crystalwallet/models/CryptoCoinTransaction.java @@ -59,7 +59,7 @@ public class CryptoCoinTransaction { * The amount of asset is moved in this transaction */ @ColumnInfo(name="amount") - protected int amount; + protected long amount; /** * The id of the Crypto Currency to use in the database @@ -136,9 +136,9 @@ public class CryptoCoinTransaction { public void setConfirmed(boolean confirmed) { isConfirmed = confirmed; } - public int getAmount() { return amount; } + public long getAmount() { return amount; } - public void setAmount(int amount) { this.amount = amount; } + public void setAmount(long amount) { this.amount = amount; } public int getIdCurrency() { return idCurrency; }