diff --git a/app/build.gradle b/app/build.gradle index 44270e6..f8fa21f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,8 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' // add this line + kapt { generateStubs = true @@ -15,8 +17,8 @@ android { applicationId "cy.agorise.crystalwallet" minSdkVersion 21 targetSdkVersion 27 - versionCode 4 - versionName "0.4M.alpha" + versionCode 3 + versionName "0.3M.alpha" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b185b0d..8739183 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ + -1) { 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 815e70f..4a8a2de 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/BoardActivity.java @@ -98,6 +98,14 @@ public class BoardActivity extends CustomActivity { File photoDirectory; + /* + * For the window animation + * */ + private MediaPlayer mediaPlayer; + + + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -170,7 +178,7 @@ public class BoardActivity extends CustomActivity { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { //Log.d(TAG,"surfaceCreated"); - MediaPlayer mediaPlayer = MediaPlayer.create(BoardActivity.this, R.raw.appbar_background); + mediaPlayer = MediaPlayer.create(BoardActivity.this, R.raw.appbar_background); mediaPlayer.setDisplay(mSurfaceView.getHolder()); mediaPlayer.setLooping(true); mediaPlayer.start(); @@ -271,6 +279,17 @@ public class BoardActivity extends CustomActivity { loadUserImage(); } + @Override + protected void onDestroy() { + super.onDestroy(); + + //Release the media player + if(mediaPlayer!=null){ + mediaPlayer.release(); + mediaPlayer = null; + } + } + public void loadUserImage(){ //Search for a existing photo File photoFile = new File(photoDirectory + File.separator + "photo.png"); diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/CreateSeedActivity.kt b/app/src/main/java/cy/agorise/crystalwallet/activities/CreateSeedActivity.kt index 1f3f535..fdeaf06 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/CreateSeedActivity.kt +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/CreateSeedActivity.kt @@ -200,6 +200,7 @@ class CreateSeedActivity : CustomActivity() { val intent = Intent(applicationContext, BackupSeedActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK intent.putExtra("SEED_ID", accountSeed.id) + intent.putExtra("newAccount", true) startActivity(intent) } else if (request.status == ValidateCreateBitsharesAccountRequest.StatusCode.ACCOUNT_EXIST) { diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/CryptoNetAccountSettingsActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/CryptoNetAccountSettingsActivity.java index 460cfdc..429f6f4 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/CryptoNetAccountSettingsActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/CryptoNetAccountSettingsActivity.java @@ -56,6 +56,14 @@ public class CryptoNetAccountSettingsActivity extends AppCompatActivity{ private CryptoNetAccount cryptoNetAccount; + /* + * For the window animation + * */ + private MediaPlayer mediaPlayer; + + + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -98,7 +106,7 @@ public class CryptoNetAccountSettingsActivity extends AppCompatActivity{ @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { //Log.d(TAG,"surfaceCreated"); - MediaPlayer mediaPlayer = MediaPlayer.create(CryptoNetAccountSettingsActivity.this, R.raw.appbar_background); + mediaPlayer = MediaPlayer.create(CryptoNetAccountSettingsActivity.this, R.raw.appbar_background); mediaPlayer.setDisplay(mSurfaceView.getHolder()); mediaPlayer.setLooping(true); mediaPlayer.start(); @@ -121,6 +129,19 @@ public class CryptoNetAccountSettingsActivity extends AppCompatActivity{ } } + + @Override + protected void onDestroy() { + super.onDestroy(); + + //Release the media player + if(mediaPlayer!=null){ + mediaPlayer.release(); + mediaPlayer = null; + } + } + + private class SettingsPagerAdapter extends FragmentStatePagerAdapter { SettingsPagerAdapter(FragmentManager fm) { super(fm); diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/ImportSeedActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/ImportSeedActivity.java index c583459..cfac83f 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/ImportSeedActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/ImportSeedActivity.java @@ -1,10 +1,12 @@ package cy.agorise.crystalwallet.activities; +import android.app.Activity; import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.Editable; +import android.text.TextWatcher; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; @@ -14,6 +16,7 @@ import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.OnTextChanged; import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.dialogs.material.CrystalLoading; import cy.agorise.crystalwallet.requestmanagers.CryptoNetInfoRequestListener; import cy.agorise.crystalwallet.requestmanagers.CryptoNetInfoRequests; import cy.agorise.crystalwallet.requestmanagers.ValidateImportBitsharesAccountRequest; @@ -53,6 +56,8 @@ public class ImportSeedActivity extends AppCompatActivity implements UIValidator @BindView(R.id.btnCancel) Button btnCancel; + final Activity activity = this; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -60,12 +65,136 @@ public class ImportSeedActivity extends AppCompatActivity implements UIValidator ButterKnife.bind(this); - btnImport.setEnabled(false); + /* + * Initially the button CREATE WALLET should be disabled + * */ + disableCreate(); + + /* + * When a text change in any of the fields + * */ + etPin.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + + /* + * If all is ready to continue enable the button, contrarie case disable it + * */ + if(allFieldsAreFill()){ + enableCreate(); + } + else{ + disableCreate(); + } + } + }); + etPinConfirmation.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + + /* + * If all is ready to continue enable the button, contrarie case disable it + * */ + if(allFieldsAreFill()){ + enableCreate(); + } + else{ + disableCreate(); + } + } + }); + etSeedWords.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + + /* + * If all is ready to continue enable the button, contrarie case disable it + * */ + if(allFieldsAreFill()){ + enableCreate(); + } + else{ + disableCreate(); + } + } + }); + etAccountName.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + + /* + * If all is ready to continue enable the button, contrarie case disable it + * */ + if(allFieldsAreFill()){ + enableCreate(); + } + else{ + disableCreate(); + } + } + }); + accountSeedViewModel = ViewModelProviders.of(this).get(AccountSeedViewModel.class); importSeedValidator = new ImportSeedValidator(this.getApplicationContext(),etPin,etPinConfirmation,etAccountName,etSeedWords); importSeedValidator.setListener(this); } + + /* + * Method to validate if all the fields are fill + * */ + private boolean allFieldsAreFill(){ + + boolean complete = false; + if( etPin.getText().toString().trim().compareTo("")!=0 && + etPinConfirmation.getText().toString().trim().compareTo("")!=0 && + etSeedWords.getText().toString().trim().compareTo("")!=0 && + etAccountName.getText().toString().trim().compareTo("")!=0){ + complete = true; + } + return complete; + } + @OnTextChanged(value = R.id.etPin, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) void afterPinChanged(Editable editable) { @@ -102,12 +231,25 @@ public class ImportSeedActivity extends AppCompatActivity implements UIValidator if (this.importSeedValidator.isValid()) { + + /* + * Loading dialog + * */ + final CrystalLoading crystalLoading = new CrystalLoading(activity); + crystalLoading.show(); + final ValidateImportBitsharesAccountRequest validatorRequest = new ValidateImportBitsharesAccountRequest(etAccountName.getText().toString(), etSeedWords.getText().toString(), getApplicationContext(), true); validatorRequest.setListener(new CryptoNetInfoRequestListener() { @Override public void onCarryOut() { + + /* + * Hide the loading dialog + * */ + crystalLoading.dismiss(); + if (!validatorRequest.getStatus().equals(ValidateImportBitsharesAccountRequest.StatusCode.SUCCEEDED)) { String errorText = "An error ocurred attempting to import the account"; @@ -182,9 +324,9 @@ public class ImportSeedActivity extends AppCompatActivity implements UIValidator } if (activity.importSeedValidator.isValid()){ - btnImport.setEnabled(true); + enableCreate(); } else { - btnImport.setEnabled(false); + disableCreate(); } } @@ -203,4 +345,31 @@ public class ImportSeedActivity extends AppCompatActivity implements UIValidator //tvSeedWordsError.setText(field.getMessage()); } } + + + /* + * Enable create button + * */ + private void enableCreate() { + runOnUiThread(new Runnable() { + @Override + public void run() { + btnImport.setBackgroundColor(getResources().getColor(R.color.colorPrimary)); + btnImport.setEnabled(true); + } + }); + } + + /* + * Disable create button + * */ + private void disableCreate() { + runOnUiThread(new Runnable() { + @Override + public void run() { + btnImport.setEnabled(false); + btnImport.setBackground(getDrawable(R.drawable.disable_style)); + } + }); + } } 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 f0e00d3..1d2d91f 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/IntroActivity.java @@ -2,9 +2,11 @@ package cy.agorise.crystalwallet.activities; import android.arch.lifecycle.ViewModelProviders; import android.content.Intent; +import android.content.pm.PackageManager; import android.media.MediaPlayer; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v7.app.AppCompatActivity; @@ -52,6 +54,14 @@ public class IntroActivity extends CustomActivity { @BindView(R.id.btnImportAccount) public Button btnImportAccount; + /* + * For the window animation + * */ + private MediaPlayer mediaPlayer; + + + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -64,7 +74,7 @@ public class IntroActivity extends CustomActivity { @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { //Log.d(TAG,"surfaceCreated"); - MediaPlayer mediaPlayer = MediaPlayer.create(IntroActivity.this, R.raw.appbar_background); + mediaPlayer = MediaPlayer.create(IntroActivity.this, R.raw.appbar_background); mediaPlayer.setDisplay(mSurfaceView.getHolder()); mediaPlayer.setLooping(true); mediaPlayer.start(); @@ -146,6 +156,17 @@ public class IntroActivity extends CustomActivity { startActivity(intent); } + @Override + protected void onDestroy() { + super.onDestroy(); + + //Release the media player + if(mediaPlayer!=null){ + mediaPlayer.release(); + mediaPlayer = null; + } + } + @OnClick(R.id.btnImportAccount) public void importAccount() { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/PatternRequestActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/PatternRequestActivity.java index 0ecb4f8..b4334dd 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/PatternRequestActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/PatternRequestActivity.java @@ -71,7 +71,8 @@ public class PatternRequestActivity extends AppCompatActivity { public void onComplete(List pattern) { if (PasswordManager.checkPassword(patternEncrypted,patternToString(pattern))){ if (CrystalSecurityMonitor.getInstance(null).is2ndFactorSet()) { - CrystalSecurityMonitor.getInstance(null).call2ndFactor(thisActivity); + //CrystalSecurityMonitor.getInstance(null).call2ndFactor(thisActivity); + thisActivity.finish(); } else { thisActivity.finish(); } diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/SendTransactionActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/SendTransactionActivity.java index f34db62..adfa17c 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/SendTransactionActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/SendTransactionActivity.java @@ -4,6 +4,7 @@ import android.arch.lifecycle.LiveData; import android.arch.lifecycle.Observer; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.design.widget.FloatingActionButton; import android.support.v7.app.AppCompatActivity; import android.text.Editable; import android.widget.Button; @@ -62,6 +63,9 @@ public class SendTransactionActivity extends AppCompatActivity implements UIVali //@BindView(R.id.btnCancel) Button btnCancel; + @BindView(R.id.fabCloseCamera) + FloatingActionButton btnCloseCamera; + private long cryptoNetAccountId; private CryptoNetAccount cryptoNetAccount; private GrapheneAccount grapheneAccount; @@ -146,6 +150,15 @@ public class SendTransactionActivity extends AppCompatActivity implements UIVali this.finish(); } + + + @OnClick(R.id.fabCloseCamera) + public void onClicCloseCamera(){ + + + } + + //@OnClick(R.id.btnSend) public void importSend(){ if (this.sendTransactionValidator.isValid()) { diff --git a/app/src/main/java/cy/agorise/crystalwallet/activities/SettingsActivity.java b/app/src/main/java/cy/agorise/crystalwallet/activities/SettingsActivity.java index a953b05..a17931c 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/activities/SettingsActivity.java +++ b/app/src/main/java/cy/agorise/crystalwallet/activities/SettingsActivity.java @@ -50,6 +50,14 @@ public class SettingsActivity extends AppCompatActivity{ private SecuritySettingsFragment securitySettingsFragment; + /* + * For the window animation + * */ + private MediaPlayer mediaPlayer; + + + + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -64,7 +72,7 @@ public class SettingsActivity extends AppCompatActivity{ @Override public void surfaceCreated(SurfaceHolder surfaceHolder) { //Log.d(TAG,"surfaceCreated"); - MediaPlayer mediaPlayer = MediaPlayer.create(SettingsActivity.this, R.raw.appbar_background); + mediaPlayer = MediaPlayer.create(SettingsActivity.this, R.raw.appbar_background); mediaPlayer.setDisplay(mSurfaceView.getHolder()); mediaPlayer.setLooping(true); mediaPlayer.start(); @@ -123,6 +131,17 @@ public class SettingsActivity extends AppCompatActivity{ } } + @Override + protected void onDestroy() { + super.onDestroy(); + + //Release the media player + if(mediaPlayer != null){ + mediaPlayer.release(); + mediaPlayer = null; + } + } + @OnClick(R.id.ivGoBack) public void goBack(){ onBackPressed(); diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/InsightApiGenerator.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/InsightApiGenerator.java index 54fe88e..7a5505f 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/InsightApiGenerator.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/InsightApiGenerator.java @@ -1,47 +1,74 @@ package cy.agorise.crystalwallet.apigenerator; -import android.content.Context; - import java.util.HashMap; +import cy.agorise.crystalwallet.apigenerator.insightapi.AddressesActivityWatcher; import cy.agorise.crystalwallet.apigenerator.insightapi.BroadcastTransaction; import cy.agorise.crystalwallet.apigenerator.insightapi.GetEstimateFee; import cy.agorise.crystalwallet.apigenerator.insightapi.GetTransactionByAddress; -import cy.agorise.crystalwallet.apigenerator.insightapi.GetTransactionData; -import cy.agorise.crystalwallet.enums.CryptoNet; +import cy.agorise.crystalwallet.enums.CryptoCoin; import cy.agorise.crystalwallet.network.CryptoNetManager; public class InsightApiGenerator { - private static HashMap broadcaster = new HashMap(); - private static HashMap transactionGetters = new HashMap(); - private static HashMap transacitonFollowers = new HashMap(); + private static HashMap transactionGetters = new HashMap(); + private static HashMap transactionFollowers = new HashMap(); - public static void getTransactionFromAddress(CryptoNet cryptoNet, String address, - ApiRequest request, Context context, - boolean subscribe){ - if(!transactionGetters.containsKey(cryptoNet)){ - //TODO change this line - transactionGetters.put(cryptoNet,new GetTransactionByAddress(null,CryptoNetManager.getURL(cryptoNet),context)); + /** + * Fecth all the transaciton for a giving address + * @param cryptoCoin the crypto net of the address + * @param address The address String + * @param request the request api to response + * @param subscribe If needs to follow the address (Real time) + */ + public static void getTransactionFromAddress(CryptoCoin cryptoCoin, String address, + ApiRequest request, boolean subscribe){ + if(!transactionGetters.containsKey(cryptoCoin)){ + transactionGetters.put(cryptoCoin,new GetTransactionByAddress(cryptoCoin,CryptoNetManager.getURL(cryptoCoin.getCryptoNet()))); } - - } - - public static void followTransaction(CryptoNet cryptoNet, String txid, Context context){ - - } - - public static void broadcastTransaction(CryptoNet cryptoNet, String rawtx, ApiRequest request){ - if(!broadcaster.containsKey(cryptoNet)){ - //TODO change to multiple broadcast - broadcaster.put(cryptoNet,new BroadcastTransaction(rawtx,null, - CryptoNetManager.getURL(cryptoNet),null)); - broadcaster.get(cryptoNet).start(); + transactionGetters.get(cryptoCoin).addAddress(address); + transactionGetters.get(cryptoCoin).start(); + if(subscribe){ + if(!transactionFollowers.containsKey(cryptoCoin)){ + transactionFollowers.put(cryptoCoin,new AddressesActivityWatcher(CryptoNetManager.getURL(cryptoCoin.getCryptoNet()),cryptoCoin)); + } + transactionFollowers.get(cryptoCoin).addAddress(address); + transactionFollowers.get(cryptoCoin).connect(); } } - public static void getEstimateFee(CryptoNet cryptoNet, final ApiRequest request){ - GetEstimateFee.getEstimateFee(CryptoNetManager.getURL(cryptoNet), new GetEstimateFee.estimateFeeListener() { + /** + * Broadcast an insight api transaction + * @param cryptoCoin The cryptoNet of the transaction + * @param rawtx the transaction to be broadcasted + */ + public static void broadcastTransaction(CryptoCoin cryptoCoin, String rawtx, final ApiRequest request){ + BroadcastTransaction bTransaction = new BroadcastTransaction(rawtx, + CryptoNetManager.getURL(cryptoCoin.getCryptoNet()), "api", new BroadcastTransaction.BroadCastTransactionListener() { + @Override + public void onSuccess() { + request.getListener().success(true,request.getId()); + } + + @Override + public void onFailure(String msg) { + request.getListener().fail(request.getId()); + } + + @Override + public void onConnecitonFailure() { + request.getListener().fail(request.getId()); + } + }); + bTransaction.start(); + } + + /** + * Fetch the estimated fee for a transaction + */ + public static void getEstimateFee(CryptoCoin cryptoCoin, final ApiRequest request){ + GetEstimateFee.getEstimateFee(CryptoNetManager.getURL(cryptoCoin.getCryptoNet()), + new GetEstimateFee.estimateFeeListener() { @Override public void estimateFee(long value) { request.listener.success(value,request.getId()); diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/AccountActivityWatcher.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/AddressesActivityWatcher.java similarity index 79% rename from app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/AccountActivityWatcher.java rename to app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/AddressesActivityWatcher.java index 1745efb..719c412 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/AccountActivityWatcher.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/AddressesActivityWatcher.java @@ -12,6 +12,7 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import cy.agorise.crystalwallet.enums.CryptoCoin; import cy.agorise.crystalwallet.models.GeneralCoinAccount; import io.socket.client.IO; import io.socket.client.Socket; @@ -24,12 +25,9 @@ import io.socket.emitter.Emitter; * */ -public class AccountActivityWatcher { +public class AddressesActivityWatcher { - /** - * The mAccount to be monitor - */ - private final GeneralCoinAccount mAccount; + private final CryptoCoin cryptoCoin; /** * The list of address to monitor */ @@ -38,10 +36,6 @@ public class AccountActivityWatcher { * the Socket.IO */ private Socket mSocket; - /** - * This app mContext, used to save on the DB - */ - private final Context mContext; private final String mServerUrl; @@ -55,9 +49,9 @@ public class AccountActivityWatcher { try { System.out.println("Receive accountActivtyWatcher " + os[0].toString() ); String txid = ((JSONObject) os[0]).getString(InsightApiConstants.sTxTag); - new GetTransactionData(txid, mAccount, mServerUrl, mContext).start(); + new GetTransactionData(txid, mServerUrl, cryptoCoin).start(); } catch (JSONException ex) { - Logger.getLogger(AccountActivityWatcher.class.getName()).log(Level.SEVERE, null, ex); + Logger.getLogger(AddressesActivityWatcher.class.getName()).log(Level.SEVERE, null, ex); } } }; @@ -84,7 +78,9 @@ public class AccountActivityWatcher { private final Emitter.Listener onDisconnect = new Emitter.Listener() { @Override public void call(Object... os) { - System.out.println("Disconnected to accountActivityWatcher"); + try { + Thread.sleep(60000); + } catch (InterruptedException ignore) {} mSocket.connect(); } }; @@ -99,19 +95,20 @@ public class AccountActivityWatcher { for(Object ob : os) { System.out.println("accountActivityWatcher " + ob.toString()); } + try { + Thread.sleep(60000); + } catch (InterruptedException ignore) {} + mSocket.connect(); } }; /** * Basic constructor * - * @param mAccount The mAccount to be monitor - * @param mContext This app mContext */ - public AccountActivityWatcher(String serverUrl, GeneralCoinAccount mAccount, Context mContext) { + public AddressesActivityWatcher(String serverUrl, CryptoCoin cryptoCoin) { this.mServerUrl = serverUrl; - this.mAccount = mAccount; - this.mContext = mContext; + this.cryptoCoin = cryptoCoin; try { this.mSocket = IO.socket(serverUrl); this.mSocket.on(Socket.EVENT_CONNECT, onConnect); @@ -141,13 +138,11 @@ public class AccountActivityWatcher { * Connects the Socket */ public void connect() { - //TODO change to use log - System.out.println("accountActivityWatcher connecting"); try{ - this.mSocket.connect(); - }catch(Exception e){ - //TODO change exception handler - System.out.println("accountActivityWatcher exception " + e.getMessage()); + if(this.mSocket == null || !this.mSocket.connected()) { + this.mSocket.connect(); + } + }catch(Exception ignore){ } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/BroadcastTransaction.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/BroadcastTransaction.java index 1736b6a..c4fa9cc 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/BroadcastTransaction.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/BroadcastTransaction.java @@ -22,29 +22,21 @@ public class BroadcastTransaction extends Thread implements Callback { * The serviceGenerator to call */ private InsightApiServiceGenerator mServiceGenerator; - /** - * This app context, used to save on the DB - */ - private Context mContext; - /** - * The account who sign the transaction - */ - private GeneralCoinAccount mAccount; - private String serverUrl; + private String mPath; + + private BroadCastTransactionListener listener; /** * Basic Consturctor * @param RawTx The RawTX in Hex String - * @param account The account who signs the transaction - * @param context This app context + * */ - public BroadcastTransaction(String RawTx, GeneralCoinAccount account, String serverUrl, Context context){ - this.serverUrl = serverUrl; + public BroadcastTransaction(String RawTx, String serverUrl, String path, BroadCastTransactionListener listener){ this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl); - this.mContext = context; this.mRawTx = RawTx; - this.mAccount = account; + this.listener = listener; + this.mPath = path; } /** @@ -54,13 +46,9 @@ public class BroadcastTransaction extends Thread implements Callback { @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { - //TODO invalidated send - //TODO call getTransactionData - GetTransactionData trData = new GetTransactionData(response.body().txid,this.mAccount, this.serverUrl, this.mContext); - trData.start(); + listener.onSuccess(); } else { - System.out.println("SENDTEST: not succesful " + response.message()); - //TODO change how to handle invalid transaction + listener.onFailure(response.message()); } } @@ -69,8 +57,7 @@ public class BroadcastTransaction extends Thread implements Callback { */ @Override public void onFailure(Call call, Throwable t) { - //TODO change how to handle invalid transaction - System.out.println("SENDTEST: sendError " + t.getMessage() ); + listener.onConnecitonFailure(); } /** @@ -79,7 +66,13 @@ public class BroadcastTransaction extends Thread implements Callback { @Override public void run() { InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class); - Call broadcastTransaction = service.broadcastTransaction(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),this.mRawTx); + Call broadcastTransaction = service.broadcastTransaction(this.mPath,this.mRawTx); broadcastTransaction.enqueue(this); } + + public interface BroadCastTransactionListener{ + void onSuccess(); + void onFailure(String msg); + void onConnecitonFailure(); + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetTransactionByAddress.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetTransactionByAddress.java index 8d92b39..5ca3894 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetTransactionByAddress.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetTransactionByAddress.java @@ -3,14 +3,21 @@ package cy.agorise.crystalwallet.apigenerator.insightapi; import android.content.Context; import android.util.Log; +import com.idescout.sql.SqlScoutServer; + import java.util.ArrayList; import java.util.Date; import java.util.List; +import cy.agorise.crystalwallet.apigenerator.InsightApiGenerator; import cy.agorise.crystalwallet.apigenerator.insightapi.models.AddressTxi; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout; +import cy.agorise.crystalwallet.enums.CryptoCoin; +import cy.agorise.crystalwallet.enums.CryptoNet; +import cy.agorise.crystalwallet.manager.GeneralAccountManager; +import cy.agorise.crystalwallet.models.CryptoCurrency; import cy.agorise.crystalwallet.models.GTxIO; import cy.agorise.crystalwallet.models.GeneralCoinAccount; import cy.agorise.crystalwallet.models.GeneralCoinAddress; @@ -25,43 +32,34 @@ import retrofit2.Response; */ public class GetTransactionByAddress extends Thread implements Callback { - /** - * The account to be query - */ - private GeneralCoinAccount mAccount; /** * The list of address to query */ - private List mAddresses = new ArrayList<>(); + private List mAddresses = new ArrayList<>(); /** * The serviceGenerator to call */ private InsightApiServiceGenerator mServiceGenerator; - /** - * This app context, used to save on the DB - */ - private Context mContext; private String serverUrl; + private CryptoCoin cryptoNet; + private boolean inProcess = false; /** * Basic consturcotr - * @param account The account to be query - * @param context This app context */ - public GetTransactionByAddress(GeneralCoinAccount account, String serverUrl, Context context) { + public GetTransactionByAddress(CryptoCoin cryptoNet, String serverUrl) { + this.cryptoNet = cryptoNet; this.serverUrl = serverUrl; - this.mAccount = account; this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl); - this.mContext = context; } /** * add an address to be query * @param address the address to be query */ - public void addAddress(GeneralCoinAddress address) { + public void addAddress(String address) { this.mAddresses.add(address); } @@ -73,110 +71,15 @@ public class GetTransactionByAddress extends Thread implements Callback call, Response response) { + inProcess = false; if (response.isSuccessful()) { boolean changed = false; AddressTxi addressTxi = response.body(); for (Txi txi : addressTxi.items) { - GeneralCoinAccount tempAccount = null; - GeneralTransaction transaction = new GeneralTransaction(); - transaction.setAccount(this.mAccount); - transaction.setTxid(txi.txid); - transaction.setBlock(txi.blockheight); - transaction.setDate(new Date(txi.time * 1000)); - transaction.setFee((long) (txi.fee * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision()))); - transaction.setConfirm(txi.confirmations); - transaction.setType(this.mAccount.getCryptoCoin()); - transaction.setBlockHeight(txi.blockheight); - - for (Vin vin : txi.vin) { - GTxIO input = new GTxIO(); - input.setAmount((long) (vin.value * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision()))); - input.setTransaction(transaction); - input.setOut(true); - input.setType(this.mAccount.getCryptoCoin()); - String addr = vin.addr; - input.setAddressString(addr); - input.setIndex(vin.n); - input.setScriptHex(vin.scriptSig.hex); - input.setOriginalTxid(vin.txid); - for (GeneralCoinAddress address : this.mAddresses) { - if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) { - input.setAddress(address); - tempAccount = address.getAccount(); - - if (!address.hasTransactionOutput(input, this.mAccount.getNetworkParam())) { - address.getTransactionOutput().add(input); - } - changed = true; - } - } - transaction.getTxInputs().add(input); - } - - for (Vout vout : txi.vout) { - if(vout.scriptPubKey.addresses == null || vout.scriptPubKey.addresses.length <= 0){ - // The address is null, this must be a memo - String hex = vout.scriptPubKey.hex; - int opReturnIndex = hex.indexOf("6a"); - if(opReturnIndex >= 0) { - byte[] memoBytes = new byte[Integer.parseInt(hex.substring(opReturnIndex+2,opReturnIndex+4),16)]; - for(int i = 0; i < memoBytes.length;i++){ - memoBytes[i] = Byte.parseByte(hex.substring(opReturnIndex+4+(i*2),opReturnIndex+6+(i*2)),16); - } - transaction.setMemo(new String(memoBytes)); - } - }else { - GTxIO output = new GTxIO(); - output.setAmount((long) (vout.value * Math.pow(10, this.mAccount.getCryptoCoin().getPrecision()))); - output.setTransaction(transaction); - output.setOut(false); - output.setType(this.mAccount.getCryptoCoin()); - String addr = vout.scriptPubKey.addresses[0]; - output.setAddressString(addr); - output.setIndex(vout.n); - output.setScriptHex(vout.scriptPubKey.hex); - for (GeneralCoinAddress address : this.mAddresses) { - if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) { - output.setAddress(address); - tempAccount = address.getAccount(); - - if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) { - address.getTransactionInput().add(output); - } - changed = true; - } - } - - transaction.getTxOutputs().add(output); - } - } - if(txi.txlock && txi.confirmations< this.mAccount.getCryptoNet().getConfirmationsNeeded()){ - transaction.setConfirm(this.mAccount.getCryptoNet().getConfirmationsNeeded()); - } - //TODO database - /*SCWallDatabase db = new SCWallDatabase(this.mContext); - long idTransaction = db.getGeneralTransactionId(transaction); - if (idTransaction == -1) { - db.putGeneralTransaction(transaction); - } else { - transaction.setId(idTransaction); - db.updateGeneralTransaction(transaction); - }*/ - - if (tempAccount != null && transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) { - new GetTransactionData(transaction.getTxid(), tempAccount, this.serverUrl, this.mContext, true).start(); - } - for (GeneralCoinAddress address : this.mAddresses) { - if (address.updateTransaction(transaction)) { - break; - } - } + GeneralAccountManager.getAccountManager(this.cryptoNet).processTxi(txi); } - if(changed) { - this.mAccount.balanceChange(); - } } } @@ -187,6 +90,7 @@ public class GetTransactionByAddress extends Thread implements Callback call, Throwable t) { + inProcess = false; Log.e("GetTransactionByAddress", "Error in json format"); } @@ -195,14 +99,15 @@ public class GetTransactionByAddress extends Thread implements Callback 0) { + if (this.mAddresses.size() > 0 && !inProcess) { + inProcess = true; StringBuilder addressToQuery = new StringBuilder(); - for (GeneralCoinAddress address : this.mAddresses) { - addressToQuery.append(address.getAddressString(this.mAccount.getNetworkParam())).append(","); + for (String address : this.mAddresses) { + addressToQuery.append(address).append(","); } addressToQuery.deleteCharAt(addressToQuery.length() - 1); InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class); - Call addressTxiCall = service.getTransactionByAddress(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),addressToQuery.toString()); + Call addressTxiCall = service.getTransactionByAddress(this.serverUrl,addressToQuery.toString()); addressTxiCall.enqueue(this); } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetTransactionData.java b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetTransactionData.java index d50d5dc..2286c0b 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetTransactionData.java +++ b/app/src/main/java/cy/agorise/crystalwallet/apigenerator/insightapi/GetTransactionData.java @@ -7,6 +7,8 @@ import java.util.Date; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin; import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout; +import cy.agorise.crystalwallet.enums.CryptoCoin; +import cy.agorise.crystalwallet.manager.GeneralAccountManager; import cy.agorise.crystalwallet.models.GTxIO; import cy.agorise.crystalwallet.models.GeneralCoinAccount; import cy.agorise.crystalwallet.models.GeneralCoinAddress; @@ -20,10 +22,6 @@ import retrofit2.Response; */ public class GetTransactionData extends Thread implements Callback { - /** - * The account to be query - */ - private final GeneralCoinAccount mAccount; /** * The transaction txid to be query */ @@ -32,10 +30,6 @@ public class GetTransactionData extends Thread implements Callback { * The serviceGenerator to call */ private InsightApiServiceGenerator mServiceGenerator; - /** - * This app context, used to save on the DB - */ - private Context mContext; private String mServerUrl; /** @@ -43,30 +37,28 @@ public class GetTransactionData extends Thread implements Callback { */ private boolean mMustWait = false; + private CryptoCoin cryptoCoin; + /** * Constructor used to query for a transaction with unknown confirmations * @param txid The txid of the transaciton to be query - * @param account The account to be query - * @param context This app Context */ - public GetTransactionData(String txid, GeneralCoinAccount account,String serverUrl, Context context) { - this(txid, account, serverUrl, context, false); + public GetTransactionData(String txid, String serverUrl, CryptoCoin cryptoCoin) { + this(txid, serverUrl, cryptoCoin, false); + } /** * Consturctor to be used qhen the confirmations of the transaction are known * @param txid The txid of the transaciton to be query - * @param account The account to be query - * @param context This app Context * @param mustWait If there is less confirmation that needed */ - public GetTransactionData(String txid, GeneralCoinAccount account,String serverUrl, Context context, boolean mustWait) { + public GetTransactionData(String txid, String serverUrl, CryptoCoin cryptoCoin, boolean mustWait) { this.mServerUrl = serverUrl; - this.mAccount = account; this.mTxId= txid; this.mServiceGenerator = new InsightApiServiceGenerator(serverUrl); - this.mContext = context; this.mMustWait = mustWait; + this.cryptoCoin = cryptoCoin; } /** @@ -84,104 +76,19 @@ public class GetTransactionData extends Thread implements Callback { } InsightApiService service = this.mServiceGenerator.getService(InsightApiService.class); - Call txiCall = service.getTransaction(InsightApiConstants.getPath(this.mAccount.getCryptoCoin()),this.mTxId); + Call txiCall = service.getTransaction(this.mServerUrl,this.mTxId); txiCall.enqueue(this); } @Override public void onResponse(Call call, Response response) { if (response.isSuccessful()) { + Txi txi = response.body(); - - GeneralTransaction transaction = new GeneralTransaction(); - transaction.setAccount(this.mAccount); - transaction.setTxid(txi.txid); - transaction.setBlock(txi.blockheight); - transaction.setDate(new Date(txi.time * 1000)); - transaction.setFee((long) (txi.fee * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision()))); - transaction.setConfirm(txi.confirmations); - transaction.setType(this.mAccount.getCryptoCoin()); - transaction.setBlockHeight(txi.blockheight); - - for (Vin vin : txi.vin) { - GTxIO input = new GTxIO(); - input.setAmount((long) (vin.value * Math.pow(10,this.mAccount.getCryptoCoin().getPrecision()))); - input.setTransaction(transaction); - input.setOut(true); - input.setType(this.mAccount.getCryptoCoin()); - String addr = vin.addr; - input.setAddressString(addr); - input.setIndex(vin.n); - input.setScriptHex(vin.scriptSig.hex); - input.setOriginalTxid(vin.txid); - for (GeneralCoinAddress address : this.mAccount.getAddresses()) { - if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) { - input.setAddress(address); - if (!address.hasTransactionOutput(input, this.mAccount.getNetworkParam())) { - address.getTransactionOutput().add(input); - } - } - } - transaction.getTxInputs().add(input); - } - - for (Vout vout : txi.vout) { - if(vout.scriptPubKey.addresses == null || vout.scriptPubKey.addresses.length <= 0){ - // The address is null, this must be a memo - String hex = vout.scriptPubKey.hex; - int opReturnIndex = hex.indexOf("6a"); - if(opReturnIndex >= 0) { - byte[] memoBytes = new byte[Integer.parseInt(hex.substring(opReturnIndex+2,opReturnIndex+4),16)]; - for(int i = 0; i < memoBytes.length;i++){ - memoBytes[i] = Byte.parseByte(hex.substring(opReturnIndex+4+(i*2),opReturnIndex+6+(i*2)),16); - } - transaction.setMemo(new String(memoBytes)); - System.out.println("Memo read : " + transaction.getMemo()); //TODO log this line - } - - }else { - GTxIO output = new GTxIO(); - output.setAmount((long) (vout.value * Math.pow(10, this.mAccount.getCryptoCoin().getPrecision()))); - output.setTransaction(transaction); - output.setOut(false); - output.setType(this.mAccount.getCryptoCoin()); - String addr = vout.scriptPubKey.addresses[0]; - output.setAddressString(addr); - output.setIndex(vout.n); - output.setScriptHex(vout.scriptPubKey.hex); - for (GeneralCoinAddress address : this.mAccount.getAddresses()) { - if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) { - output.setAddress(address); - if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) { - address.getTransactionInput().add(output); - } - } - } - transaction.getTxOutputs().add(output); - } - } - - // This is for features like dash instantSend - if(txi.txlock && txi.confirmations< this.mAccount.getCryptoNet().getConfirmationsNeeded()){ - transaction.setConfirm(this.mAccount.getCryptoNet().getConfirmationsNeeded()); - } - - //TODO database - /*SCWallDatabase db = new SCWallDatabase(this.mContext); - long idTransaction = db.getGeneralTransactionId(transaction); - if (idTransaction == -1) { - db.putGeneralTransaction(transaction); - } else { - transaction.setId(idTransaction); - db.updateGeneralTransaction(transaction); - }*/ - - this.mAccount.updateTransaction(transaction); - this.mAccount.balanceChange(); - - if (transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) { + GeneralAccountManager.getAccountManager(this.cryptoCoin).processTxi(txi); + if (txi.confirmations < this.cryptoCoin.getCryptoNet().getConfirmationsNeeded()) { //If transaction weren't confirmed, add the transaction to watch for change on the confirmations - new GetTransactionData(this.mTxId, this.mAccount, this.mServerUrl, this.mContext, true).start(); + new GetTransactionData(this.mTxId, this.mServerUrl, this.cryptoCoin, true).start(); } } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinTransactionDao.java b/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinTransactionDao.java new file mode 100644 index 0000000..5b62286 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/BitcoinTransactionDao.java @@ -0,0 +1,26 @@ +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; +import android.arch.persistence.room.Query; + +import cy.agorise.crystalwallet.models.BitcoinTransaction; +import cy.agorise.crystalwallet.models.BitcoinTransactionExtended; + +/** + * Created by Henry Varona on 10/02/2018. + */ +@Dao +public interface BitcoinTransactionDao { + + @Query("SELECT * FROM crypto_coin_transaction cct, bitcoin_transaction bt WHERE bt.crypto_coin_transaction_id = cct.id") + LiveData getAll(); + + @Query("SELECT * FROM bitcoin_transaction bt WHERE bt.tx_id = :txid") + BitcoinTransaction getByTxid(String txid); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + public long[] insertBitcoinTransaction(BitcoinTransaction... transactions); +} 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 cd8840a..7dc40c0 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java +++ b/app/src/main/java/cy/agorise/crystalwallet/dao/CrystalDatabase.java @@ -10,6 +10,8 @@ import android.content.Context; import cy.agorise.crystalwallet.dao.converters.Converters; import cy.agorise.crystalwallet.models.AccountSeed; +import cy.agorise.crystalwallet.models.BitcoinTransaction; +import cy.agorise.crystalwallet.models.BitcoinTransactionGTxIO; import cy.agorise.crystalwallet.models.BitsharesAccountNameCache; import cy.agorise.crystalwallet.models.BitsharesAssetInfo; import cy.agorise.crystalwallet.models.Contact; @@ -39,8 +41,10 @@ import cy.agorise.crystalwallet.models.GrapheneAccountInfo; BitsharesAssetInfo.class, BitsharesAccountNameCache.class, CryptoCurrencyEquivalence.class, - GeneralSetting.class -}, version = 4, exportSchema = false) + GeneralSetting.class, + BitcoinTransaction.class, + BitcoinTransactionGTxIO.class +}, version = 5, exportSchema = false) @TypeConverters({Converters.class}) public abstract class CrystalDatabase extends RoomDatabase { @@ -57,6 +61,7 @@ public abstract class CrystalDatabase extends RoomDatabase { public abstract BitsharesAccountNameCacheDao bitsharesAccountNameCacheDao(); public abstract CryptoCurrencyEquivalenceDao cryptoCurrencyEquivalenceDao(); public abstract GeneralSettingDao generalSettingDao(); + public abstract BitcoinTransactionDao bitcoinTransactionDao(); public static CrystalDatabase getAppDatabase(Context context) { if (instance == null) { @@ -66,6 +71,7 @@ public abstract class CrystalDatabase extends RoomDatabase { .allowMainThreadQueries() .addMigrations(MIGRATION_2_3) .addMigrations(MIGRATION_3_4) + .addMigrations(MIGRATION_4_5) .build(); } return instance; @@ -91,4 +97,25 @@ public abstract class CrystalDatabase extends RoomDatabase { } }; + + static final Migration MIGRATION_4_5 = new Migration(4, 5) { + @Override + public void migrate(SupportSQLiteDatabase database) { + database.execSQL("CREATE TABLE bitcoin_transaction (" + +"crypto_coin_transaction_id INTEGER PRIMARY KEY NOT NULL," + +"tx_id TEXT NOT NULL," + +"block INTEGER NOT NULL," + +"fee INTEGER NOT NULL," + +"confirmations INTEGER NOT NULL," + +"FOREIGN KEY (crypto_coin_transaction_id) REFERENCES crypto_coin_transaction(id) ON DELETE CASCADE)"); + + database.execSQL("CREATE TABLE bitcoin_transaction_gt_io (" + +"bitcoin_transaction_id INTEGER NOT NULL," + +"io_index INTEGER NOT NULL," + +"address TEXT," + +"is_output INTEGER NOT NULL," + +"PRIMARY KEY (bitcoin_transaction_id, io_index, is_output)," + +"FOREIGN KEY (bitcoin_transaction_id) REFERENCES bitcoin_transaction(crypto_coin_transaction_id) ON DELETE CASCADE)"); + } + }; } diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/BalanceFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/BalanceFragment.java index c696b49..8522f47 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/BalanceFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/BalanceFragment.java @@ -71,7 +71,7 @@ public class BalanceFragment extends Fragment { vCryptoNetBalanceListView.setData(cryptoNetBalances, fragment); final int size = cryptoNetBalances.size(); - if(size==1){ + if(size==0){ tvNobalances.setVisibility(View.VISIBLE); } else{ diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralCryptoNetAccountSettingsFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralCryptoNetAccountSettingsFragment.java index 2c182a9..d51e3e5 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralCryptoNetAccountSettingsFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/GeneralCryptoNetAccountSettingsFragment.java @@ -1,6 +1,10 @@ package cy.agorise.crystalwallet.fragments; +import android.app.Activity; import android.arch.lifecycle.LiveData; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; @@ -8,11 +12,13 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; +import android.widget.Toast; import java.util.List; import butterknife.BindView; import butterknife.ButterKnife; +import butterknife.OnClick; import cy.agorise.crystalwallet.R; import cy.agorise.crystalwallet.dao.CrystalDatabase; import cy.agorise.crystalwallet.models.AccountSeed; @@ -30,9 +36,15 @@ public class GeneralCryptoNetAccountSettingsFragment extends Fragment { @BindView(R.id.tvMnemonic) TextView tvMnemonic; + @BindView(R.id.btnCopy) + Button btnCopy; + CryptoNetAccount cryptoNetAccount; AccountSeed accountSeed; + + + public GeneralCryptoNetAccountSettingsFragment() { if (getArguments() != null) { @@ -83,4 +95,24 @@ public class GeneralCryptoNetAccountSettingsFragment extends Fragment { tvMnemonic.setText(this.accountSeed.getMasterSeed()); } } + + /* + * Clic on button copy to clipboard + * */ + @OnClick(R.id.btnCopy) + public void btnCopyClick(){ + + /* + * Save to clipboard the brainkey chain + * */ + final Activity activity = getActivity(); + ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(tvMnemonic.getText(), tvMnemonic.getText().toString()); + clipboard.setPrimaryClip(clip); + + /* + * Success message + * */ + Toast.makeText(activity,getResources().getString(R.string.window_seed_toast_clipboard), Toast.LENGTH_SHORT).show(); + } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/ImportAccountOptionsFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/ImportAccountOptionsFragment.java index 4b1e38c..845cb9c 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/ImportAccountOptionsFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/ImportAccountOptionsFragment.java @@ -5,12 +5,17 @@ import android.app.Dialog; import android.arch.lifecycle.ViewModelProviders; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.v4.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -19,6 +24,8 @@ import android.widget.Button; import android.widget.EditText; import android.widget.Toast; +import com.vincent.filepicker.ToastUtil; + import java.net.URISyntaxException; import butterknife.BindView; @@ -50,6 +57,7 @@ public class ImportAccountOptionsFragment extends DialogFragment { @BindView(R.id.btnImportBackup) Button btnImportBackup; + private static final int PERMISSION_REQUEST_CODE = 1; /* Dialog for loading @@ -110,18 +118,88 @@ public class ImportAccountOptionsFragment extends DialogFragment { @OnClick (R.id.btnImportBackup) public void importBackup(){ - Intent fileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - fileIntent.setType("*/*"); - fileIntent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(fileIntent, FILE_CONTENT_REQUEST_CODE); + if (Build.VERSION.SDK_INT >= 23) { + if (checkPermission()) { + + Intent fileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + fileIntent.setType("*/*"); + fileIntent.addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(fileIntent, FILE_CONTENT_REQUEST_CODE); + + } else { + requestPermission(); // Code for permission + } + } + else { + + Intent fileIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + fileIntent.setType("*/*"); + fileIntent.addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(fileIntent, FILE_CONTENT_REQUEST_CODE); + } } @OnClick (R.id.btnImportSeed) public void importSeed(){ - Intent intent = new Intent(this.getActivity(), ImportSeedActivity.class); - startActivity(intent); + + if (Build.VERSION.SDK_INT >= 23) { + + if (checkPermission()) { + + Intent intent = new Intent(this.getActivity(), ImportSeedActivity.class); + startActivity(intent); + + } else { + requestPermission(); // Code for permission + } + } + else { + Intent intent = new Intent(this.getActivity(), ImportSeedActivity.class); + startActivity(intent); + } + } + + + private boolean checkPermission() { + int result = ContextCompat.checkSelfPermission(getActivity(), android.Manifest.permission.WRITE_EXTERNAL_STORAGE); + if (result == PackageManager.PERMISSION_GRANTED) { + return true; + } else { + return false; + } + } + + + private void requestPermission() { + + Log.i("log", "requestPermission() entered"); + + if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), android.Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + Toast.makeText(getActivity(), "Write External Storage permission allows us to do store images. Please allow this permission in App Settings.", Toast.LENGTH_LONG).show(); + } else { + // ActivityCompat.requestPermissions(getActivity(), new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); + requestPermissions(new String[] {android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); + } + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + + switch (requestCode) { + case PERMISSION_REQUEST_CODE: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + + Intent intent = new Intent(getActivity(), ImportSeedActivity.class); + startActivity(intent); + + } else { + ToastUtil.getInstance(getActivity()).showToast(getActivity().getString(R.string.Permission_Denied_WRITE_EXTERNAL_STORAGE)); + } + break; + } } @Override diff --git a/app/src/main/java/cy/agorise/crystalwallet/fragments/SendTransactionFragment.java b/app/src/main/java/cy/agorise/crystalwallet/fragments/SendTransactionFragment.java index 3055135..56b6600 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/fragments/SendTransactionFragment.java +++ b/app/src/main/java/cy/agorise/crystalwallet/fragments/SendTransactionFragment.java @@ -8,11 +8,13 @@ import android.arch.lifecycle.Observer; import android.arch.lifecycle.ViewModelProviders; import android.content.Context; import android.content.ContextWrapper; -import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; @@ -22,20 +24,20 @@ import android.support.v4.app.ActivityCompat; import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.text.Editable; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.Window; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; -import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; +import android.widget.Toast; import com.google.zxing.BarcodeFormat; import com.google.zxing.Result; @@ -56,6 +58,8 @@ import butterknife.OnClick; import butterknife.OnItemSelected; import butterknife.OnTextChanged; import cy.agorise.crystalwallet.R; +import cy.agorise.crystalwallet.dialogs.material.CrystalDialog; +import cy.agorise.crystalwallet.dialogs.material.ToastIt; import cy.agorise.crystalwallet.requestmanagers.CryptoNetInfoRequestListener; import cy.agorise.crystalwallet.requestmanagers.CryptoNetInfoRequests; import cy.agorise.crystalwallet.requestmanagers.ValidateBitsharesSendRequest; @@ -93,6 +97,8 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat View viewSend; @BindView(R.id.tvToError) TextView tvToError; + @BindView(R.id.fabCloseCamera) + FloatingActionButton btnCloseCamera; @BindView(R.id.spAsset) Spinner spAsset; @BindView(R.id.tvAssetError) @@ -122,6 +128,14 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat @BindView(R.id.gravatar) CircularImageView userImg; + @BindView(R.id.viewCamera) + View viewCamera; + + /* + * Flag to control when the camera is visible and when is hide + * */ + private boolean cameraVisible = true; + Button btnScanQrCode; private long cryptoNetAccountId; @@ -131,7 +145,10 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat private FloatingActionButton fabSend; private AlertDialog.Builder builder; - + /* + Dialog for loading + */ + private CrystalDialog crystalDialog; public static SendTransactionFragment newInstance(long cryptoNetAccountId) { SendTransactionFragment f = new SendTransactionFragment(); @@ -273,19 +290,123 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat } loadUserImage(); - try { - verifyCameraPermissions(getActivity()); - beginScanQrCode(); - }catch(Exception e){ - e.printStackTrace(); + + /* + * Check for CAMERA permission + * */ + if (Build.VERSION.SDK_INT >= 23) { + if (checkPermission()) { + // Code for above or equal 23 API Oriented Device + // Your Permission granted already .Do next code + + /* + * Init the camera + * */ + try { + beginScanQrCode(); + }catch(Exception e){ + e.printStackTrace(); + } + + } else { + requestPermission(); // Code for permission + } + } + else { + + // Code for Below 23 API Oriented Device + // Do next code + + /* + * Init the camera + * */ + try { + beginScanQrCode(); + }catch(Exception e){ + e.printStackTrace(); + } } return builder.setView(view).create(); } + + private void requestPermission() { + + if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.CAMERA)) { + Toast.makeText(getActivity(), getActivity().getString(R.string.permission_denied_camera), Toast.LENGTH_LONG).show(); + + /* + * Disable the button of the camera visibility + * */ + disableVisibilityCamera(); + + } else { + requestPermissions(new String[] {android.Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION); + } + } + + private void disableVisibilityCamera(){ + + /* + * Hide the button, the user can not modify the visibility + * */ + btnCloseCamera.setVisibility(View.INVISIBLE); + } + + private boolean checkPermission() { + int result = ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA); + if (result == PackageManager.PERMISSION_GRANTED) { + return true; + } else { + return false; + } + } + + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + switch (requestCode) { + case REQUEST_CAMERA_PERMISSION: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.e("value", "Permission Granted, Now you can use camera ."); + + getActivity().runOnUiThread(new Runnable(){ + public void run() { + Toast.makeText(getActivity(), getActivity().getString(R.string.permission_granted_camera), Toast.LENGTH_LONG).show(); + } + }); + + /* + * Init the camera + * */ + try { + beginScanQrCode(); + }catch(Exception e){ + e.printStackTrace(); + } + + } else { + Log.e("value", "Permission Denied, You cannot use the camera."); + + getActivity().runOnUiThread(new Runnable(){ + public void run() { + Toast.makeText(getActivity(), getActivity().getString(R.string.permission_denied_camera), Toast.LENGTH_LONG).show(); + } + }); + } + break; + } + } + @Override public void onResume() { super.onResume(); + mScannerView.setResultHandler(this); + mScannerView.startCamera(); /*builder.setNeutralButton("Scan QR Code", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { @@ -300,6 +421,12 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat loadUserImage(); } + @Override + public void onPause() { + super.onPause(); + mScannerView.stopCamera(); + } + @Override public void onDestroy() { super.onDestroy(); @@ -351,6 +478,71 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat } + @OnClick(R.id.fabCloseCamera) + public void onClicCloseCamera(){ + mScannerView.stopCamera(); + + /* + * Hide the camera or show it + * */ + if(cameraVisible){ + hideCamera(); + } + else{ + showCamera(); + } + } + + /* + * Show the camera and hide the black background + * */ + private void showCamera(){ + + /* + * Change visibilities of views + * */ + viewCamera.setVisibility(View.GONE); + mScannerView.setVisibility(View.VISIBLE); + + /* + * Change icon + * */ + btnCloseCamera.setImageDrawable(getResources().getDrawable(R.drawable.ic_close)); + + /* + * Reset variable + * */ + cameraVisible = true; + + /* + * Star the camera again + * */ + beginScanQrCode(); + } + + + /* + * Hide the camera and show the black background + * */ + private void hideCamera(){ + + /* + * Change visibilities of views + * */ + viewCamera.setVisibility(View.VISIBLE); + mScannerView.setVisibility(View.INVISIBLE); + + /* + * Change icon + * */ + btnCloseCamera.setImageDrawable(getResources().getDrawable(R.drawable.ok)); + + /* + * Reset variable + * */ + cameraVisible = false; + } + @OnTextChanged(value = R.id.etMemo, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED) void afterMemoChanged(Editable editable) { @@ -408,6 +600,8 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat @OnClick(R.id.btnSend) public void sendTransaction(){ + final SendTransactionFragment thisFragment = this; + if (this.sendTransactionValidator.isValid()) { CryptoNetAccount fromAccountSelected = (CryptoNetAccount) spFrom.getItems().get(spFrom.getSelectedIndex()); @@ -439,13 +633,26 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat public void onCarryOut() { if (sendRequest.getStatus().equals(ValidateBitsharesSendRequest.StatusCode.SUCCEEDED)){ try { - this.finalize(); + crystalDialog.dismiss(); + thisFragment.dismiss(); + //thisFragment.finalize(); } catch (Throwable throwable) { throwable.printStackTrace(); } + } else { + Toast.makeText(getContext(), getContext().getString(R.string.unable_to_send_amount), Toast.LENGTH_LONG); } } }); + + /* + * Show loading dialog + * */ + crystalDialog = new CrystalDialog((Activity) getContext()); + crystalDialog.setText("Sending"); + crystalDialog.progress(); + crystalDialog.show(); + CryptoNetInfoRequests.getInstance().addRequest(sendRequest); } } @@ -453,6 +660,7 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat public void beginScanQrCode(){ //mScannerView = new ZXingScannerView(getContext()); mScannerView.setFormats(listOf(BarcodeFormat.QR_CODE)); + mScannerView.setAspectTolerance(0.5f); mScannerView.setAutoFocus(true); mScannerView.setLaserColor(R.color.colorAccent); mScannerView.setMaskColor(R.color.colorAccent); @@ -467,20 +675,6 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat }; - public static void verifyCameraPermissions(Activity activity) { - // Check if we have write permission - int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.CAMERA); - - if (permission != PackageManager.PERMISSION_GRANTED) { - // We don't have permission so prompt the user - ActivityCompat.requestPermissions( - activity, - PERMISSIONS_CAMERA, - REQUEST_CAMERA_PERMISSION - ); - } - } - @Override public void onValidationSucceeded(final ValidationField field) { final SendTransactionFragment fragment = this; @@ -534,7 +728,6 @@ public class SendTransactionFragment extends DialogFragment implements UIValidat @Override public void handleResult(Result result) { try { - System.out.println("CAMERA result " + result.getText() ); Invoice invoice = Invoice.fromQrCode(result.getText()); etTo.setText(invoice.getTo()); 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 a3871fb..ec5edb2 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/BitsharesAccountManager.java @@ -2,6 +2,7 @@ package cy.agorise.crystalwallet.manager; import android.annotation.SuppressLint; import android.content.Context; +import android.util.Log; import com.google.common.primitives.UnsignedLong; @@ -20,6 +21,7 @@ import cy.agorise.crystalwallet.apigenerator.GrapheneApiGenerator; import cy.agorise.crystalwallet.apigenerator.grapheneoperation.AccountUpgradeOperationBuilder; import cy.agorise.crystalwallet.application.constant.BitsharesConstant; import cy.agorise.crystalwallet.dao.AccountSeedDao; +import cy.agorise.crystalwallet.enums.CryptoCoin; import cy.agorise.crystalwallet.models.BitsharesAccountNameCache; import cy.agorise.crystalwallet.models.seed.BIP39; import cy.agorise.crystalwallet.requestmanagers.CryptoNetEquivalentRequest; @@ -64,8 +66,6 @@ import cy.agorise.graphenej.operations.TransferOperationBuilder; */ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetInfoRequestsListener { - //private final static String BITSHARES_TESTNET_CHAIN_ID= "9cf6f255a208100d2bb275a3c52f4b1589b7ec9c9bfc2cb2a5fe6411295106d8"; - private final static String SIMPLE_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; private final static String DEFAULT_TIME_ZONE = "GMT"; @@ -129,7 +129,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI long[] idAccount = db.cryptoNetAccountDao().insertCryptoNetAccount(grapheneAccount); grapheneAccount.setId(idAccount[0]); db.grapheneAccountInfoDao().insertGrapheneAccountInfo(new GrapheneAccountInfo(grapheneAccount)); - subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context); + //subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context); } @Override @@ -147,7 +147,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI CrystalDatabase db = CrystalDatabase.getAppDatabase(context); db.cryptoNetAccountDao().insertCryptoNetAccount(grapheneAccount); db.grapheneAccountInfoDao().insertGrapheneAccountInfo(new GrapheneAccountInfo(grapheneAccount)); - subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context); + //subscribeBitsharesAccount(grapheneAccount.getId(),grapheneAccount.getAccountId(),context); } @Override @@ -159,7 +159,7 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI CrystalDatabase db = CrystalDatabase.getAppDatabase(context); db.cryptoNetAccountDao().insertCryptoNetAccount(grapheneAccount); db.grapheneAccountInfoDao().insertGrapheneAccountInfo(new GrapheneAccountInfo(grapheneAccount)); - subscribeBitsharesAccount(grapheneAccount.getId(), grapheneAccount.getAccountId(), context); + //subscribeBitsharesAccount(grapheneAccount.getId(), grapheneAccount.getAccountId(), context); } } } @@ -221,24 +221,26 @@ public class BitsharesAccountManager implements CryptoAccountManager, CryptoNetI */ @Override public void onNewRequest(CryptoNetInfoRequest request) { - if (request instanceof ValidateImportBitsharesAccountRequest){ - this.validateImportAccount((ValidateImportBitsharesAccountRequest) request); - } else if (request instanceof ValidateExistBitsharesAccountRequest){ - this.validateExistAcccount((ValidateExistBitsharesAccountRequest) request); - } else if (request instanceof ValidateBitsharesSendRequest){ - this.validateSendRequest((ValidateBitsharesSendRequest) request); - }else if (request instanceof CryptoNetEquivalentRequest){ - this.getEquivalentValue((CryptoNetEquivalentRequest) request); - }else if (request instanceof ValidateCreateBitsharesAccountRequest){ - this.validateCreateAccount((ValidateCreateBitsharesAccountRequest) request); - }else if (request instanceof ValidateBitsharesLTMUpgradeRequest){ - this.validateLTMAccountUpgrade((ValidateBitsharesLTMUpgradeRequest) request); - }else if (request instanceof GetBitsharesAccountNameCacheRequest){ - this.getBitsharesAccountNameCacheRequest((GetBitsharesAccountNameCacheRequest) request); - }else{ + if(request.getCoin().equals(CryptoCoin.BITSHARES)) { + if (request instanceof ValidateImportBitsharesAccountRequest) { + this.validateImportAccount((ValidateImportBitsharesAccountRequest) request); + } else if (request instanceof ValidateExistBitsharesAccountRequest) { + this.validateExistAcccount((ValidateExistBitsharesAccountRequest) request); + } else if (request instanceof ValidateBitsharesSendRequest) { + this.validateSendRequest((ValidateBitsharesSendRequest) request); + } else if (request instanceof CryptoNetEquivalentRequest) { + this.getEquivalentValue((CryptoNetEquivalentRequest) request); + } else if (request instanceof ValidateCreateBitsharesAccountRequest) { + this.validateCreateAccount((ValidateCreateBitsharesAccountRequest) request); + } else if (request instanceof ValidateBitsharesLTMUpgradeRequest) { + this.validateLTMAccountUpgrade((ValidateBitsharesLTMUpgradeRequest) request); + } else if (request instanceof GetBitsharesAccountNameCacheRequest) { + this.getBitsharesAccountNameCacheRequest((GetBitsharesAccountNameCacheRequest) request); + } else { - //TODO not implemented - System.out.println("Error request not implemented " + request.getClass().getName()); + //TODO not implemented + System.out.println("Error request not implemented " + request.getClass().getName()); + } } } diff --git a/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java b/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java index 1a4c6fa..8beb806 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java +++ b/app/src/main/java/cy/agorise/crystalwallet/manager/GeneralAccountManager.java @@ -12,14 +12,25 @@ import org.bitcoinj.crypto.HDKeyDerivation; import org.bitcoinj.script.Script; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; import java.util.List; import cy.agorise.crystalwallet.apigenerator.ApiRequest; import cy.agorise.crystalwallet.apigenerator.ApiRequestListener; import cy.agorise.crystalwallet.apigenerator.InsightApiGenerator; import cy.agorise.crystalwallet.apigenerator.insightapi.BroadcastTransaction; +import cy.agorise.crystalwallet.apigenerator.insightapi.GetTransactionData; +import cy.agorise.crystalwallet.apigenerator.insightapi.models.Txi; +import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vin; +import cy.agorise.crystalwallet.apigenerator.insightapi.models.Vout; +import cy.agorise.crystalwallet.dao.CrystalDatabase; +import cy.agorise.crystalwallet.enums.CryptoCoin; +import cy.agorise.crystalwallet.models.BitcoinTransaction; import cy.agorise.crystalwallet.models.CryptoNetAccount; import cy.agorise.crystalwallet.models.GTxIO; +import cy.agorise.crystalwallet.models.GeneralCoinAccount; import cy.agorise.crystalwallet.models.GeneralCoinAddress; import cy.agorise.crystalwallet.models.GeneralTransaction; import cy.agorise.crystalwallet.requestmanagers.CryptoNetInfoRequest; @@ -29,6 +40,28 @@ import cy.agorise.graphenej.Util; public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInfoRequestsListener { + static HashMap generalAccountManagers = new HashMap(); + + private static CryptoCoin[] SUPPORTED_COINS = new CryptoCoin[]{ + CryptoCoin.BITCOIN, + CryptoCoin.BITCOIN_TEST, + CryptoCoin.DASH, + CryptoCoin.LITECOIN + } ; + + final CryptoCoin cryptoCoin; + final Context context; + + public static GeneralAccountManager getAccountManager(CryptoCoin coin){ + return generalAccountManagers.get(coin); + } + + public GeneralAccountManager(CryptoCoin cryptoCoin, Context context) { + this.cryptoCoin = cryptoCoin; + this.context = context; + generalAccountManagers.put(cryptoCoin,this); + } + @Override public void createAccountFromSeed(CryptoNetAccount account, ManagerRequest request, Context context) { @@ -46,14 +79,131 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf @Override public void onNewRequest(CryptoNetInfoRequest request) { + if(Arrays.asList(SUPPORTED_COINS).contains(request.getCoin())){ + if(request instanceof GeneralAccountSendRequest){ + this.send((GeneralAccountSendRequest)request); + }else{ + System.out.println("Invalid " +this.cryptoCoin.getLabel() + " request "); + } + } + + } + + /** + * Class that process each transaction fetched by the insight api + * @param txi + */ + public void processTxi(Txi txi){ + CrystalDatabase db = CrystalDatabase.getAppDatabase(this.context); + BitcoinTransaction btTransaction = db.bitcoinTransactionDao().getByTxid(txi.txid); + if(btTransaction != null){ + btTransaction.setConfirmations(txi.confirmations); + db.bitcoinTransactionDao().insertBitcoinTransaction(btTransaction); + }else { + + + GeneralTransaction transaction = new GeneralTransaction(); + //transaction.setAccount(this.mAccount); + transaction.setTxid(txi.txid); + transaction.setBlock(txi.blockheight); + transaction.setDate(new Date(txi.time * 1000)); + transaction.setFee((long) (txi.fee * Math.pow(10, cryptoCoin.getPrecision()))); + transaction.setConfirm(txi.confirmations); + transaction.setType(cryptoCoin); + transaction.setBlockHeight(txi.blockheight); + + for (Vin vin : txi.vin) { + GTxIO input = new GTxIO(); + input.setAmount((long) (vin.value * Math.pow(10, cryptoCoin.getPrecision()))); + input.setTransaction(transaction); + input.setOut(true); + input.setType(cryptoCoin); + String addr = vin.addr; + input.setAddressString(addr); + input.setIndex(vin.n); + input.setScriptHex(vin.scriptSig.hex); + input.setOriginalTxid(vin.txid); + /*for (GeneralCoinAddress address : this.mAddresses) { + if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) { + input.setAddress(address); + tempAccount = address.getAccount(); + + if (!address.hasTransactionOutput(input, this.mAccount.getNetworkParam())) { + address.getTransactionOutput().add(input); + } + } + }*/ + transaction.getTxInputs().add(input); + } + + for (Vout vout : txi.vout) { + if (vout.scriptPubKey.addresses == null || vout.scriptPubKey.addresses.length <= 0) { + // The address is null, this must be a memo + String hex = vout.scriptPubKey.hex; + int opReturnIndex = hex.indexOf("6a"); + if (opReturnIndex >= 0) { + byte[] memoBytes = new byte[Integer.parseInt(hex.substring(opReturnIndex + 2, opReturnIndex + 4), 16)]; + for (int i = 0; i < memoBytes.length; i++) { + memoBytes[i] = Byte.parseByte(hex.substring(opReturnIndex + 4 + (i * 2), opReturnIndex + 6 + (i * 2)), 16); + } + transaction.setMemo(new String(memoBytes)); + } + } else { + GTxIO output = new GTxIO(); + output.setAmount((long) (vout.value * Math.pow(10, cryptoCoin.getPrecision()))); + output.setTransaction(transaction); + output.setOut(false); + output.setType(cryptoCoin); + String addr = vout.scriptPubKey.addresses[0]; + output.setAddressString(addr); + output.setIndex(vout.n); + output.setScriptHex(vout.scriptPubKey.hex); + /*for (GeneralCoinAddress address : this.mAddresses) { + if (address.getAddressString(this.mAccount.getNetworkParam()).equals(addr)) { + output.setAddress(address); + tempAccount = address.getAccount(); + + if (!address.hasTransactionInput(output, this.mAccount.getNetworkParam())) { + address.getTransactionInput().add(output); + } + changed = true; + } + }*/ + + transaction.getTxOutputs().add(output); + } + } + if (txi.txlock && txi.confirmations < cryptoCoin.getCryptoNet().getConfirmationsNeeded()) { + transaction.setConfirm(cryptoCoin.getCryptoNet().getConfirmationsNeeded()); + } + //TODO database + /*SCWallDatabase db = new SCWallDatabase(this.mContext); + long idTransaction = db.getGeneralTransactionId(transaction); + if (idTransaction == -1) { + db.putGeneralTransaction(transaction); + } else { + transaction.setId(idTransaction); + db.updateGeneralTransaction(transaction); + }*/ + + /*if (tempAccount != null && transaction.getConfirm() < this.mAccount.getCryptoNet().getConfirmationsNeeded()) { + InsightApiGenerator.followTransaction(); + new GetTransactionData(transaction.getTxid(), tempAccount, this.serverUrl, this.mContext, true).start(); + } + for (GeneralCoinAddress address : this.mAddresses) { + if (address.updateTransaction(transaction)) { + break; + } + }*/ + } } public void send(final GeneralAccountSendRequest request){ //TODO check server connection //TODO validate to address - InsightApiGenerator.getEstimateFee(request.getAccount().getCryptoNet(),new ApiRequest(1, new ApiRequestListener() { + InsightApiGenerator.getEstimateFee(request.getAccount().getCryptoCoin(),new ApiRequest(1, new ApiRequestListener() { @Override public void success(Object answer, int idPetition) { Transaction tx = new Transaction(request.getAccount().getNetworkParam()); @@ -122,7 +272,7 @@ public class GeneralAccountManager implements CryptoAccountManager, CryptoNetInf tx.addSignedInput(outPoint, script, utxo.getAddress().getKey(), Transaction.SigHash.ALL, true); } - InsightApiGenerator.broadcastTransaction(request.getAccount().getCryptoNet(),Util.bytesToHex(tx.bitcoinSerialize()),new ApiRequest(1, new ApiRequestListener() { + InsightApiGenerator.broadcastTransaction(request.getAccount().getCryptoCoin(),Util.bytesToHex(tx.bitcoinSerialize()),new ApiRequest(1, new ApiRequestListener() { @Override public void success(Object answer, int idPetition) { request.setStatus(GeneralAccountSendRequest.StatusCode.SUCCEEDED); diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransaction.java b/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransaction.java new file mode 100644 index 0000000..e7ed29b --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransaction.java @@ -0,0 +1,110 @@ +package cy.agorise.crystalwallet.models; + +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.arch.persistence.room.Ignore; +import android.arch.persistence.room.Index; +import android.arch.persistence.room.PrimaryKey; +import android.support.annotation.NonNull; +import android.support.v7.util.DiffUtil; + +import java.util.Date; + +/** + * Represents a Bitcoin alike Transaction + * + * Created by Henry Varona on 10/2/2018. + */ +@Entity( + tableName="bitcoin_transaction", + primaryKeys = {"crypto_coin_transaction_id"}, + foreignKeys = { + @ForeignKey( + entity = CryptoCoinTransaction.class, + parentColumns = "id", + childColumns = "crypto_coin_transaction_id", + onDelete = ForeignKey.CASCADE + ) + } +) +public class BitcoinTransaction { + + /** + * The id of the base transaction + */ + @ColumnInfo(name="crypto_coin_transaction_id") + protected long cryptoCoinTransactionId; + + + /** + * The transaction id in the blockchain + */ + @ColumnInfo(name="tx_id") + @NonNull protected String txId; + + /** + * The block id in the blockchain + */ + @ColumnInfo(name="block") + protected long block; + + /** + * The fee of the transaction + */ + @ColumnInfo(name="fee") + protected long fee; + /** + * The confirmations of the transaction in the blockchain + */ + @ColumnInfo(name="confirmations") + protected int confirmations; + + public BitcoinTransaction(long cryptoCoinTransactionId, String txId, long block, long fee, int confirmations) { + this.cryptoCoinTransactionId = cryptoCoinTransactionId; + this.txId = txId; + this.block = block; + this.fee = fee; + this.confirmations = confirmations; + } + + public long getCryptoCoinTransactionId() { + return cryptoCoinTransactionId; + } + + public void setCryptoCoinTransactionId(long cryptoCoinTransactionId) { + this.cryptoCoinTransactionId = cryptoCoinTransactionId; + } + + public String getTxId() { + return txId; + } + + public void setTxId(String txId) { + this.txId = txId; + } + + public long getBlock() { + return block; + } + + public void setBlock(long block) { + this.block = block; + } + + public long getFee() { + return fee; + } + + public void setFee(long fee) { + this.fee = fee; + } + + public int getConfirmations() { + return confirmations; + } + + public void setConfirmations(int confirmations) { + this.confirmations = confirmations; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransactionExtended.java b/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransactionExtended.java new file mode 100644 index 0000000..c2202bc --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransactionExtended.java @@ -0,0 +1,26 @@ +package cy.agorise.crystalwallet.models; + +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.Embedded; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; +import android.arch.persistence.room.Relation; + +import java.util.List; + +/** + * Represents a Bitcoin alike Transaction + * + * Created by Henry Varona on 10/2/2018. + */ +public class BitcoinTransactionExtended { + + @Embedded + public CryptoCoinTransaction cryptoCoinTransaction; + + @Embedded + public BitcoinTransaction bitcoinTransaction; + + @Relation(parentColumn = "id", entityColumn = "bitcoin_transaction_id", entity = BitcoinTransactionGTxIO.class) + public List bitcoinTransactionGTxIOList; +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransactionGTxIO.java b/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransactionGTxIO.java new file mode 100644 index 0000000..8febad2 --- /dev/null +++ b/app/src/main/java/cy/agorise/crystalwallet/models/BitcoinTransactionGTxIO.java @@ -0,0 +1,88 @@ +package cy.agorise.crystalwallet.models; + +import android.arch.persistence.room.ColumnInfo; +import android.arch.persistence.room.Entity; +import android.arch.persistence.room.ForeignKey; + +/** + * Represents a Bitcoin alike Transaction Inputs and Outputs + * + * Created by Henry Varona on 10/2/2018. + */ +@Entity( + tableName="bitcoin_transaction_gt_io", + primaryKeys = {"bitcoin_transaction_id", "io_index", "is_output"}, + foreignKeys = { + @ForeignKey( + entity = BitcoinTransaction.class, + parentColumns = "crypto_coin_transaction_id", + childColumns = "bitcoin_transaction_id", + onDelete = ForeignKey.CASCADE + ) + } +) +public class BitcoinTransactionGTxIO { + + /** + * The id of the bitcoin transaction + */ + @ColumnInfo(name="bitcoin_transaction_id") + protected long bitcoinTransactionId; + + /** + * The index in the transaction + */ + @ColumnInfo(name="io_index") + protected int index; + + /** + * The address of the input or output + */ + @ColumnInfo(name="address") + protected String address; + + /** + * determines if this is an input or output + */ + @ColumnInfo(name="is_output") + protected boolean isOutput; + + public BitcoinTransactionGTxIO(long bitcoinTransactionId, int index, String address, boolean isOutput) { + this.bitcoinTransactionId = bitcoinTransactionId; + this.index = index; + this.address = address; + this.isOutput = isOutput; + } + + public long getBitcoinTransactionId() { + return bitcoinTransactionId; + } + + public void setBitcoinTransactionId(long bitcoinTransactionId) { + this.bitcoinTransactionId = bitcoinTransactionId; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public boolean isOutput() { + return isOutput; + } + + public void setOutput(boolean output) { + isOutput = output; + } +} diff --git a/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/CryptoNetInfoRequest.java b/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/CryptoNetInfoRequest.java index f45a5b1..23f88f8 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/CryptoNetInfoRequest.java +++ b/app/src/main/java/cy/agorise/crystalwallet/requestmanagers/CryptoNetInfoRequest.java @@ -21,13 +21,6 @@ public abstract class CryptoNetInfoRequest { */ protected CryptoNetInfoRequestListener listener; - - - - - - - protected CryptoNetInfoRequest(CryptoCoin coin){ this.coin = coin; } @@ -43,4 +36,7 @@ public abstract class CryptoNetInfoRequest { CryptoNetInfoRequests.getInstance().removeRequest(this); } + public CryptoCoin getCoin() { + return coin; + } } 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 954933b..fbab252 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoCoinBalanceViewHolder.java @@ -105,12 +105,19 @@ public class CryptoCoinBalanceViewHolder extends RecyclerView.ViewHolder { public void onChanged(@Nullable CryptoCurrencyEquivalence cryptoCurrencyEquivalence) { if (cryptoCurrencyEquivalence != null) { CryptoCurrency toCurrency = CrystalDatabase.getAppDatabase(context).cryptoCurrencyDao().getById(cryptoCurrencyEquivalence.getFromCurrencyId()); - double equivalentValue = (balance.getBalance() / Math.pow(10, currencyFrom.getPrecision())) / - (cryptoCurrencyEquivalence.getValue() / Math.pow(10, toCurrency.getPrecision())); - String equivalenceString = String.format( - "%.2f", - equivalentValue - ); + double equivalentValue = 0; + String equivalenceString = ""; + if (cryptoCurrencyEquivalence.getValue() > 0) { + equivalentValue = (balance.getBalance() / Math.pow(10, currencyFrom.getPrecision())) / + (cryptoCurrencyEquivalence.getValue() / Math.pow(10, toCurrency.getPrecision())); + equivalenceString = String.format( + "%.2f", + equivalentValue + ); + } else { + equivalentValue = 0; + equivalenceString = "0"; + } cryptoNetBalanceViewHolder.setEquivalentBalance(balance,equivalentValue); cryptoCoinBalanceEquivalence.setText( diff --git a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoNetBalanceViewHolder.java b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoNetBalanceViewHolder.java index 4a2043c..4a2e126 100644 --- a/app/src/main/java/cy/agorise/crystalwallet/views/CryptoNetBalanceViewHolder.java +++ b/app/src/main/java/cy/agorise/crystalwallet/views/CryptoNetBalanceViewHolder.java @@ -3,6 +3,7 @@ package cy.agorise.crystalwallet.views; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.arch.lifecycle.LifecycleOwner; import android.arch.lifecycle.LiveData; @@ -24,12 +25,14 @@ import butterknife.ButterKnife; import butterknife.OnClick; import cy.agorise.crystalwallet.R; import cy.agorise.crystalwallet.activities.SendTransactionActivity; +import cy.agorise.crystalwallet.dao.CrystalDatabase; import cy.agorise.crystalwallet.fragments.ReceiveTransactionFragment; import cy.agorise.crystalwallet.fragments.SendTransactionFragment; import cy.agorise.crystalwallet.models.CryptoCoinBalance; import cy.agorise.crystalwallet.models.CryptoCoinTransaction; import cy.agorise.crystalwallet.models.CryptoCurrencyEquivalence; import cy.agorise.crystalwallet.models.CryptoNetBalance; +import cy.agorise.crystalwallet.models.GeneralSetting; import cy.agorise.crystalwallet.viewmodels.CryptoCoinBalanceListViewModel; /** @@ -86,6 +89,8 @@ public class CryptoNetBalanceViewHolder extends RecyclerView.ViewHolder { */ private Fragment fragment; + String preferredCurrency = ""; + public CryptoNetBalanceViewHolder(View itemView, Fragment fragment) { super(itemView); //-1 represents a crypto net account not loaded yet @@ -114,6 +119,18 @@ public class CryptoNetBalanceViewHolder extends RecyclerView.ViewHolder { }); this.fragment = fragment; this.context = itemView.getContext(); + + LiveData preferedCurrencySetting = CrystalDatabase.getAppDatabase(this.context).generalSettingDao().getByName(GeneralSetting.SETTING_NAME_PREFERRED_CURRENCY); + + preferedCurrencySetting.observe((LifecycleOwner)this.itemView.getContext(), new Observer() { + @Override + public void onChanged(@Nullable GeneralSetting generalSetting) { + if(generalSetting != null){ + preferredCurrency = generalSetting.getValue(); + } + } + }); + } public void clear(){ @@ -179,7 +196,7 @@ public class CryptoNetBalanceViewHolder extends RecyclerView.ViewHolder { totalEquivalent += nextEquivalent; } - this.cryptoNetEquivalentTotal.setText(""+totalEquivalent); + this.cryptoNetEquivalentTotal.setText(""+totalEquivalent+" "+preferredCurrency); } } diff --git a/app/src/main/res/layout/crypto_net_account_activity_settings.xml b/app/src/main/res/layout/crypto_net_account_activity_settings.xml index d832726..425946c 100644 --- a/app/src/main/res/layout/crypto_net_account_activity_settings.xml +++ b/app/src/main/res/layout/crypto_net_account_activity_settings.xml @@ -56,8 +56,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="8dp" - android:text="Account Settings" - android:textColor="@color/gray" + android:text="@string/Account_Settings" + android:textColor="@color/semiTransparentWhite" android:textSize="18sp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/fragment_general_crypto_net_account_settings.xml b/app/src/main/res/layout/fragment_general_crypto_net_account_settings.xml index 023303d..fde0910 100644 --- a/app/src/main/res/layout/fragment_general_crypto_net_account_settings.xml +++ b/app/src/main/res/layout/fragment_general_crypto_net_account_settings.xml @@ -1,32 +1,67 @@ - - + android:background="@color/white" + android:padding="20dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true"> - - + + + + + + + + +