Several changes

- List generation delay on currency choice activity reduced
- Sort currencies by market order in currencies selection activity
- Rewritten suggestion list
- Add default icon when currencies' icons are missing
- Optimize details storage
- Enable Binance key input
- Replacing Binance library by compiled jar from API's github
- New main gradient
- Fix crash when balance requests complete before details request
This commit is contained in:
Tanguy Herbron 2018-02-15 14:55:48 +01:00
parent 1363c04202
commit 375afee570
49 changed files with 447 additions and 1752 deletions

View File

@ -34,7 +34,15 @@ dependencies {
implementation 'com.android.support:recyclerview-v7:26.1.0' implementation 'com.android.support:recyclerview-v7:26.1.0'
implementation 'com.daimajia.swipelayout:library:1.2.0@aar' implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
implementation 'com.github.armcha:SpaceNavigationView:1.6.0' implementation 'com.github.armcha:SpaceNavigationView:1.6.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.8.5'
implementation 'com.fasterxml.jackson.core:jackson-core:2.8.5'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.8.5'
implementation 'com.squareup.retrofit2:retrofit:2.2.0'
implementation 'com.squareup.retrofit2:converter-jackson:2.2.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.6.0'
implementation 'org.apache.commons:commons-lang3:3.6'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation files('C:/Users/Guitoune/Documents/GitHub/Coinfolio/libs/binance-api.jar')
} }

View File

@ -7,7 +7,7 @@ import android.support.test.runner.AndroidJUnit4;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
/** /**
* Instrumented test, which will execute on an Android device. * Instrumented test, which will execute on an Android device.

View File

@ -2,12 +2,10 @@ package com.nauk.coinfolio.Activities;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView; import android.support.design.widget.BottomNavigationView;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;

View File

@ -1,15 +1,20 @@
package com.nauk.coinfolio.Activities; package com.nauk.coinfolio.Activities;
import android.content.Intent; import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AutoCompleteTextView; import android.widget.AutoCompleteTextView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
import com.nauk.coinfolio.DataManagers.CurrencyData.Currency; import com.nauk.coinfolio.DataManagers.CurrencyData.Currency;
import com.nauk.coinfolio.LayoutManagers.CurrencyAdapter; import com.nauk.coinfolio.LayoutManagers.CurrencyAdapter;
@ -17,10 +22,13 @@ import com.nauk.coinfolio.R;
import java.util.ArrayList; import java.util.ArrayList;
public class CurrencySelectionActivity extends AppCompatActivity { public class CurrencySelectionActivity extends AppCompatActivity implements SearchView.OnQueryTextListener{
String[] currencySymbols; private String[] currencySymbols;
String[] currencyNames; private String[] currencyNames;
private CurrencyAdapter adapter;
private ListView listView;
private android.widget.Filter filter;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -37,25 +45,21 @@ public class CurrencySelectionActivity extends AppCompatActivity {
currencyNames = intent.getStringArrayExtra("currencyListNames"); currencyNames = intent.getStringArrayExtra("currencyListNames");
setTitle("Select a coin"); setTitle("Select a coin");
setupAdapter();
setupList();
SearchView searchView = findViewById(R.id.search_bar);
searchView.setIconifiedByDefault(false);
searchView.setOnQueryTextListener(this);
searchView.setSubmitButtonEnabled(false);
searchView.onActionViewExpanded();
} }
@Override private void setupAdapter()
public boolean onCreateOptionsMenu(Menu menu)
{ {
final AutoCompleteTextView searchAutoComplete = findViewById(R.id.search_bar);
searchAutoComplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Currency selectedCurrency = (Currency) adapterView.getItemAtPosition(i);
Intent intent = new Intent(CurrencySelectionActivity.this, RecordTransactionActivity.class);
intent.putExtra("coin", selectedCurrency.getName());
intent.putExtra("symbol", selectedCurrency.getSymbol());
startActivity(intent);
finish();
}
});
String[] currencyFullname = new String[currencyNames.length]; String[] currencyFullname = new String[currencyNames.length];
for(int i = 0; i < currencyFullname.length; i++) for(int i = 0; i < currencyFullname.length; i++)
@ -70,11 +74,58 @@ public class CurrencySelectionActivity extends AppCompatActivity {
currencyArrayList.add(new Currency(currencyNames[i], currencySymbols[i])); currencyArrayList.add(new Currency(currencyNames[i], currencySymbols[i]));
} }
CurrencyAdapter adapter = new CurrencyAdapter(this, currencyArrayList); adapter = new CurrencyAdapter(this, currencyArrayList);
}
private void setupList()
{
listView = findViewById(R.id.coinsPreview);
listView.setAdapter(adapter);
listView.setTextFilterEnabled(false);
filter = adapter.getFilter();
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
/*final AutoCompleteTextView searchAutoComplete = findViewById(R.id.search_bar);
searchAutoComplete.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Currency selectedCurrency = (Currency) adapterView.getItemAtPosition(i);
Intent intent = new Intent(CurrencySelectionActivity.this, RecordTransactionActivity.class);
intent.putExtra("coin", selectedCurrency.getName());
intent.putExtra("symbol", selectedCurrency.getSymbol());
startActivity(intent);
finish();
}
});
searchAutoComplete.setAdapter(adapter); searchAutoComplete.setAdapter(adapter);
searchAutoComplete.setThreshold(0); searchAutoComplete.setThreshold(0);*/
return true; return true;
} }
@Override
public boolean onQueryTextChange(String text)
{
filter.filter(text);
if (TextUtils.isEmpty(text)) {
listView.clearTextFilter();
} else {
listView.setFilterText(text);
}
return true;
}
@Override
public boolean onQueryTextSubmit(String query)
{
return false;
}
} }

View File

@ -1,10 +1,10 @@
package com.nauk.coinfolio.Activities; package com.nauk.coinfolio.Activities;
import android.app.Dialog; import android.app.Dialog;
import android.app.Fragment;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
@ -13,22 +13,21 @@ import android.support.annotation.NonNull;
import android.support.design.widget.AppBarLayout; import android.support.design.widget.AppBarLayout;
import android.support.design.widget.BottomNavigationView; import android.support.design.widget.BottomNavigationView;
import android.support.design.widget.CollapsingToolbarLayout; import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.widget.NestedScrollView;
import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.graphics.Palette; import android.support.v7.graphics.Palette;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.View;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -40,8 +39,8 @@ import com.luseen.spacenavigation.SpaceNavigationView;
import com.luseen.spacenavigation.SpaceOnClickListener; import com.luseen.spacenavigation.SpaceOnClickListener;
import com.nauk.coinfolio.DataManagers.BalanceManager; import com.nauk.coinfolio.DataManagers.BalanceManager;
import com.nauk.coinfolio.DataManagers.CurrencyData.Currency; import com.nauk.coinfolio.DataManagers.CurrencyData.Currency;
import com.nauk.coinfolio.LayoutManagers.HomeLayoutGenerator;
import com.nauk.coinfolio.DataManagers.PreferencesManager; import com.nauk.coinfolio.DataManagers.PreferencesManager;
import com.nauk.coinfolio.LayoutManagers.HomeLayoutGenerator;
import com.nauk.coinfolio.R; import com.nauk.coinfolio.R;
import java.io.IOException; import java.io.IOException;
@ -79,6 +78,7 @@ public class HomeActivity extends AppCompatActivity {
private Dialog loadingDialog; private Dialog loadingDialog;
private Handler handler; private Handler handler;
private Runnable updateRunnable; private Runnable updateRunnable;
private ViewFlipper viewFlipper;
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() { = new BottomNavigationView.OnNavigationItemSelectedListener() {
@ -148,6 +148,8 @@ public class HomeActivity extends AppCompatActivity {
toolbarLayout = findViewById(R.id.toolbar_layout); toolbarLayout = findViewById(R.id.toolbar_layout);
toolbarSubtitle = findViewById(R.id.toolbarSubtitle); toolbarSubtitle = findViewById(R.id.toolbarSubtitle);
currencyLayout = findViewById(R.id.currencyListLayout); currencyLayout = findViewById(R.id.currencyListLayout);
viewFlipper = findViewById(R.id.viewFlipperSummary);
viewFlipper.setDisplayedChild(1);
ImageButton addCurrencyButton = findViewById(R.id.floatingAddButton); ImageButton addCurrencyButton = findViewById(R.id.floatingAddButton);
ImageButton detailsButton = findViewById(R.id.switch_button); ImageButton detailsButton = findViewById(R.id.switch_button);
@ -160,11 +162,11 @@ public class HomeActivity extends AppCompatActivity {
toolbarSubtitle.setText("US$0.00"); toolbarSubtitle.setText("US$0.00");
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation_home); /*BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation_home);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener); navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
navigation.setSelectedItemId(R.id.navigation_view_list); navigation.setSelectedItemId(R.id.navigation_view_list);
navigation.setFitsSystemWindows(true); navigation.setFitsSystemWindows(true);
navigation.setItemBackgroundResource(R.color.colorAccent); navigation.setItemBackgroundResource(R.color.colorAccent);*/
//Events setup //Events setup
detailsButton.setOnClickListener(new View.OnClickListener() { detailsButton.setOnClickListener(new View.OnClickListener() {
@ -223,7 +225,6 @@ public class HomeActivity extends AppCompatActivity {
spaceNavigationView.addSpaceItem(new SpaceItem("Charts", R.drawable.ic_show_chart_black_24dp)); spaceNavigationView.addSpaceItem(new SpaceItem("Charts", R.drawable.ic_show_chart_black_24dp));
spaceNavigationView.addSpaceItem(new SpaceItem("Market Cap.", R.drawable.ic_pie_chart_black_24dp)); spaceNavigationView.addSpaceItem(new SpaceItem("Market Cap.", R.drawable.ic_pie_chart_black_24dp));
spaceNavigationView.setSpaceBackgroundColor(getResources().getColor(R.color.colorPrimary)); spaceNavigationView.setSpaceBackgroundColor(getResources().getColor(R.color.colorPrimary));
//spaceNavigationView.setCentreButtonIcon(R.drawable.ic_add_white_24dp);
spaceNavigationView.setCentreButtonIcon(R.drawable.ic_view_list_white_24dp); spaceNavigationView.setCentreButtonIcon(R.drawable.ic_view_list_white_24dp);
spaceNavigationView.setCentreButtonColor(getResources().getColor(R.color.colorAccent)); spaceNavigationView.setCentreButtonColor(getResources().getColor(R.color.colorAccent));
spaceNavigationView.setCentreButtonIconColorFilterEnabled(false); spaceNavigationView.setCentreButtonIconColorFilterEnabled(false);
@ -237,8 +238,13 @@ public class HomeActivity extends AppCompatActivity {
SpaceNavigationView nav = findViewById(R.id.space); SpaceNavigationView nav = findViewById(R.id.space);
nav.changeCurrentItem(-1); nav.changeCurrentItem(-1);
((AppBarLayout) findViewById(R.id.app_bar)).setNestedScrollingEnabled(true);
((NestedScrollView) findViewById(R.id.nestedScrollViewLayout)).setNestedScrollingEnabled(true);
((AppBarLayout) findViewById(R.id.app_bar)).setExpanded(true, true); ((AppBarLayout) findViewById(R.id.app_bar)).setExpanded(true, true);
findViewById(R.id.switch_button).setVisibility(View.VISIBLE);
viewFlipper.setDisplayedChild(1);
} }
@Override @Override
@ -248,9 +254,14 @@ public class HomeActivity extends AppCompatActivity {
//0 : Unknown //0 : Unknown
//1 : Market cap //1 : Market cap
((AppBarLayout) findViewById(R.id.app_bar)).setNestedScrollingEnabled(false); ((NestedScrollView) findViewById(R.id.nestedScrollViewLayout)).setNestedScrollingEnabled(false);
((AppBarLayout) findViewById(R.id.app_bar)).setExpanded(false, true); ((AppBarLayout) findViewById(R.id.app_bar)).setExpanded(false, true);
findViewById(R.id.switch_button).setVisibility(View.GONE);
viewFlipper.setDisplayedChild(itemIndex * 2);
} }
@Override @Override
@ -394,7 +405,9 @@ public class HomeActivity extends AppCompatActivity {
result = BitmapFactory.decodeStream(input); result = BitmapFactory.decodeStream(input);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
result = null; result = BitmapFactory.decodeResource(this.getResources(),
R.mipmap.icon_coinfolio);
result = Bitmap.createScaledBitmap(result, 50, 50, false);
} }
callBack.onSuccess(result); callBack.onSuccess(result);
@ -470,7 +483,7 @@ public class HomeActivity extends AppCompatActivity {
if(balanceManager.getTotalBalance() != null) if(balanceManager.getTotalBalance() != null)
{ {
if(coinCounter == balanceManager.getTotalBalance().size()) if(coinCounter == balanceManager.getTotalBalance().size() && detailsChecker)
{ {
for (int i = 0; i < balanceManager.getTotalBalance().size(); i++) for (int i = 0; i < balanceManager.getTotalBalance().size(); i++)
{ {
@ -502,6 +515,8 @@ public class HomeActivity extends AppCompatActivity {
{ {
ImageButton imgButton = findViewById(R.id.switch_button); ImageButton imgButton = findViewById(R.id.switch_button);
imgButton.setBackgroundColor(this.getResources().getColor(R.color.buttonColor));
if(isDetailed) if(isDetailed)
{ {
imgButton.setBackground(this.getResources().getDrawable(R.drawable.ic_unfold_less_black_24dp)); imgButton.setBackground(this.getResources().getDrawable(R.drawable.ic_unfold_less_black_24dp));
@ -517,6 +532,7 @@ public class HomeActivity extends AppCompatActivity {
private void generateSplash() private void generateSplash()
{ {
LinearLayout loadingLayout = new LinearLayout(this); LinearLayout loadingLayout = new LinearLayout(this);
loadingLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); loadingLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
loadingLayout.setGravity(Gravity.CENTER); loadingLayout.setGravity(Gravity.CENTER);
loadingLayout.setOrientation(LinearLayout.VERTICAL); loadingLayout.setOrientation(LinearLayout.VERTICAL);
@ -683,6 +699,14 @@ public class HomeActivity extends AppCompatActivity {
@Override @Override
protected Void doInBackground(Void... params) protected Void doInBackground(Void... params)
{ {
balanceManager.updateDetails(new BalanceManager.IconCallBack() {
@Override
public void onSuccess()
{
countCoins(false, true);
}
});
balanceManager.updateTotalBalance(new BalanceManager.VolleyCallBack() { balanceManager.updateTotalBalance(new BalanceManager.VolleyCallBack() {
@Override @Override
public void onSuccess() { public void onSuccess() {
@ -731,14 +755,6 @@ public class HomeActivity extends AppCompatActivity {
} }
}); });
balanceManager.updateDetails(new BalanceManager.IconCallBack() {
@Override
public void onSuccess()
{
countCoins(false, true);
}
});
return null; return null;
} }

View File

@ -1,8 +1,8 @@
package com.nauk.coinfolio.Activities; package com.nauk.coinfolio.Activities;
import android.content.Intent; import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;

View File

@ -12,10 +12,10 @@ import android.os.Bundle;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceActivity; import android.preference.PreferenceActivity;
import android.support.v7.app.ActionBar;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.preference.RingtonePreference; import android.preference.RingtonePreference;
import android.support.v7.app.ActionBar;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.MenuItem; import android.view.MenuItem;

View File

@ -12,6 +12,10 @@ import com.android.volley.VolleyError;
import com.android.volley.toolbox.JsonArrayRequest; import com.android.volley.toolbox.JsonArrayRequest;
import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley; import com.android.volley.toolbox.Volley;
import com.binance.api.client.BinanceApiClientFactory;
import com.binance.api.client.BinanceApiRestClient;
import com.binance.api.client.domain.account.Account;
import com.binance.api.client.domain.account.AssetBalance;
import com.nauk.coinfolio.DataManagers.CurrencyData.Currency; import com.nauk.coinfolio.DataManagers.CurrencyData.Currency;
import com.nauk.coinfolio.R; import com.nauk.coinfolio.R;
@ -21,6 +25,7 @@ import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -39,24 +44,24 @@ public class BalanceManager {
private String privatePoloniex; private String privatePoloniex;
final private String hitBalanceUrl = "https://api.hitbtc.com/api/2/trading/balance"; final private String hitBalanceUrl = "https://api.hitbtc.com/api/2/trading/balance";
final private String detailUrl = "https://www.cryptocompare.com/api/data/coinlist/"; final private String detailUrl = "https://www.cryptocompare.com/api/data/coinlist/";
final private String binanceBalanceUrl = "https://api.binance.com/api/v3/account";
final private String binanceTimeUrl = "https://api.binance.com/api/v1/time";
private RequestQueue requestQueue; private RequestQueue requestQueue;
private List<Currency> binanceBalance;
private List<Currency> hitBalance; private List<Currency> hitBalance;
private List<Currency> manualBalances; private List<Currency> manualBalances;
private List<Currency> totalBalance; private List<Currency> totalBalance;
private android.content.Context context; private android.content.Context context;
private Map<String, String> iconUrlList; private LinkedHashMap<String, String> coinInfosHashmap;
private Map<String, String> coinList;
private Map<String, Integer> coinIdList;
private PreferencesManager preferenceManager; private PreferencesManager preferenceManager;
private DatabaseManager databaseManager; private DatabaseManager databaseManager;
private BinanceApiClientFactory binanceApiClientFactory;
public BalanceManager(android.content.Context context) public BalanceManager(android.content.Context context)
{ {
this.context = context; this.context = context;
preferenceManager = new PreferencesManager(context); preferenceManager = new PreferencesManager(context);
requestQueue = Volley.newRequestQueue(context); requestQueue = Volley.newRequestQueue(context);
binanceBalance = new ArrayList<Currency>();
hitBalance = new ArrayList<Currency>(); hitBalance = new ArrayList<Currency>();
manualBalances = new ArrayList<Currency>(); manualBalances = new ArrayList<Currency>();
databaseManager = new DatabaseManager(context); databaseManager = new DatabaseManager(context);
@ -64,18 +69,62 @@ public class BalanceManager {
public List<String> getCurrenciesName() public List<String> getCurrenciesName()
{ {
return new ArrayList<>(coinList.values()); List<String> currenciesName = new ArrayList<>();
for (String symbol : coinInfosHashmap.keySet())
{
try {
JSONObject jsonObject = new JSONObject(coinInfosHashmap.get(symbol));
currenciesName.add(jsonObject.getString("CoinName"));
} catch (JSONException e) {
e.printStackTrace();
}
}
return currenciesName;
}
public List<String> getOrders()
{
List<String> currenciesOrder = new ArrayList<>();
for(String symbol : coinInfosHashmap.keySet())
{
try {
JSONObject jsonObject = new JSONObject(coinInfosHashmap.get(symbol));
currenciesOrder.add(jsonObject.getString("SortOrder"));
} catch (JSONException e) {
e.printStackTrace();
}
}
return currenciesOrder;
} }
public List<String> getCurrenciesSymbol() public List<String> getCurrenciesSymbol()
{ {
return new ArrayList<>(coinList.keySet()); return new ArrayList<>(coinInfosHashmap.keySet());
} }
public void updateExchangeKeys() public void updateExchangeKeys()
{ {
publicHitKey = preferenceManager.getHitBTCPublicKey(); publicHitKey = preferenceManager.getHitBTCPublicKey();
privateHitKey = preferenceManager.getHitBTCPrivateKey(); privateHitKey = preferenceManager.getHitBTCPrivateKey();
publicBinanceKey = preferenceManager.getBinancePublicKey();
privateBinanceKey = preferenceManager.getBinancePrivateKey();
}
public boolean isBinanceConfigured()
{
boolean isConfigured = true;
if(publicBinanceKey == null || privateBinanceKey == null)
{
isConfigured = false;
}
return isConfigured;
} }
public boolean isHitBTCConfigured() public boolean isHitBTCConfigured()
@ -117,19 +166,61 @@ public class BalanceManager {
public void updateTotalBalance(final VolleyCallBack callBack) public void updateTotalBalance(final VolleyCallBack callBack)
{ {
boolean isUpdated = false;
manualBalances = databaseManager.getAllCurrencyFromManualCurrency(); manualBalances = databaseManager.getAllCurrencyFromManualCurrency();
Log.d("coinfolio", "Updating balances " + (privateBinanceKey != null && publicBinanceKey != null && preferenceManager.isBinanceActivated()));
if(privateHitKey != null && publicHitKey != null && preferenceManager.isHitBTCActivated()) if(privateHitKey != null && publicHitKey != null && preferenceManager.isHitBTCActivated())
{ {
updateHitBalance(callBack); updateHitBalance(callBack);
isUpdated = true;
} }
else else
{ {
hitBalance = new ArrayList<Currency>(); hitBalance = new ArrayList<Currency>();
}
if(privateBinanceKey != null && publicBinanceKey != null && preferenceManager.isBinanceActivated())
{
Log.d("coinfolio", "Updating Binance");
updateBinanceBalance();
isUpdated = true;
}
if(!isUpdated)
{
refreshAllBalances(callBack); refreshAllBalances(callBack);
} }
} }
private void updateBinanceBalance()
{
Map<String, AssetBalance> accountBalanceCache;
BinanceApiClientFactory factory = BinanceApiClientFactory.newInstance(publicBinanceKey, privateBinanceKey);
BinanceApiRestClient client = factory.newRestClient();
Account account = client.getAccount();
List<AssetBalance> assets = account.getBalances();
binanceBalance = new ArrayList<Currency>();
for(int i = 0; i < assets.size(); i++)
{
if(Double.parseDouble(assets.get(i).getFree()) > 0)
{
binanceBalance.add(new Currency(assets.get(i).getAsset(), assets.get(i).getFree()));
}
}
Log.d("coinfolio", "Binance size : " + binanceBalance.size());
for(int i = 0; i < binanceBalance.size(); i++)
{
Log.d("coinfolio", "Binance : " + binanceBalance.get(i).getSymbol() + " " + binanceBalance.get(i).getBalance());
}
}
private void updateHitBalance(final VolleyCallBack callBack) private void updateHitBalance(final VolleyCallBack callBack)
{ {
JsonArrayRequest arrReq = new JsonArrayRequest(Request.Method.GET, hitBalanceUrl, JsonArrayRequest arrReq = new JsonArrayRequest(Request.Method.GET, hitBalanceUrl,
@ -269,10 +360,14 @@ public class BalanceManager {
String url; String url;
try { try {
url = iconUrlList.get(symbol); JSONObject jsonObject = new JSONObject(coinInfosHashmap.get(symbol));
url = "https://www.cryptocompare.com" + jsonObject.getString("ImageUrl") + "?width=50";
} catch (NullPointerException e) { } catch (NullPointerException e) {
Log.d(context.getResources().getString(R.string.debug), symbol + " has no icon URL"); Log.d(context.getResources().getString(R.string.debug), symbol + " has no icon URL");
url = null; url = null;
} catch (JSONException e) {
Log.d(context.getResources().getString(R.string.debug), "Url parsing error for " + symbol);
url = null;
} }
return url; return url;
@ -280,12 +375,30 @@ public class BalanceManager {
public String getCurrencyName(String symbol) public String getCurrencyName(String symbol)
{ {
return coinList.get(symbol); String currencyName = null;
try {
JSONObject jsonObject = new JSONObject(coinInfosHashmap.get(symbol));
currencyName = jsonObject.getString("CoinName");
} catch (JSONException e) {
e.printStackTrace();
}
return currencyName;
} }
public int getCurrencyId(String symbol) public int getCurrencyId(String symbol)
{ {
return coinIdList.get(symbol); int id = 0;
try {
JSONObject jsonObject = new JSONObject(coinInfosHashmap.get(symbol));
id = jsonObject.getInt("Id");
} catch (JSONException e) {
e.printStackTrace();
}
return id;
} }
private void processDetailResult(String response, final IconCallBack callBack) private void processDetailResult(String response, final IconCallBack callBack)
@ -293,9 +406,7 @@ public class BalanceManager {
response = response.substring(response.indexOf("\"Data\"") + 7, response.lastIndexOf("},\"Type\":100}")); response = response.substring(response.indexOf("\"Data\"") + 7, response.lastIndexOf("},\"Type\":100}"));
String[] tab = response.split(Pattern.quote("},")); String[] tab = response.split(Pattern.quote("},"));
iconUrlList = new HashMap<>(); coinInfosHashmap = new LinkedHashMap<>();
coinList = new HashMap<>();
coinIdList = new HashMap<>();
for(int i = 0; i < tab.length; i++) for(int i = 0; i < tab.length; i++)
{ {
@ -305,16 +416,42 @@ public class BalanceManager {
StrictMode.setThreadPolicy(policy); StrictMode.setThreadPolicy(policy);
JSONObject jsonObject = new JSONObject(tab[i]); JSONObject jsonObject = new JSONObject(tab[i]);
iconUrlList.put(jsonObject.getString("Symbol"), "https://www.cryptocompare.com" + jsonObject.getString("ImageUrl") + "?width=50"); coinInfosHashmap.put(jsonObject.getString("Symbol"), tab[i]);
coinList.put(jsonObject.getString("Symbol"), jsonObject.getString("CoinName"));
coinIdList.put(jsonObject.getString("Symbol"), jsonObject.getInt("Id"));
} catch (JSONException e) { } catch (JSONException e) {
Log.d(context.getResources().getString(R.string.debug), "ImageUrl not found."); Log.d(context.getResources().getString(R.string.debug), "ImageUrl not found.");
} }
} }
sortDetails();
callBack.onSuccess(); callBack.onSuccess();
} }
private void sortDetails()
{
LinkedHashMap<String, String> sortedHashmap = new LinkedHashMap<>();
List<String> listInfos = new ArrayList<>(coinInfosHashmap.values());
List<String> listSymbols = new ArrayList<>(coinInfosHashmap.keySet());
for(int i = 0; i < coinInfosHashmap.keySet().size(); i++)
{
try {
JSONObject jsonObject = new JSONObject(listInfos.get(i));
int index = jsonObject.getInt("SortOrder");
listInfos.add(index, listInfos.get(i));
listSymbols.add(index, listSymbols.get(i));
} catch (JSONException e) {
e.printStackTrace();
}
}
for(int i = 0; i < listInfos.size(); i++)
{
sortedHashmap.put(listSymbols.get(i), listInfos.get(i));
}
coinInfosHashmap = sortedHashmap;
}
} }

View File

@ -3,7 +3,6 @@ package com.nauk.coinfolio.DataManagers.CurrencyData;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.util.Log;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -5,13 +5,11 @@ import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.nauk.coinfolio.DataManagers.CurrencyData.Currency; import com.nauk.coinfolio.DataManagers.CurrencyData.Currency;
import com.nauk.coinfolio.DataManagers.CurrencyData.Transaction; import com.nauk.coinfolio.DataManagers.CurrencyData.Transaction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
/** /**

View File

@ -65,4 +65,25 @@ public class PreferencesManager {
editor.apply(); editor.apply();
} }
public String getBinancePublicKey()
{
return settingPreferences.getString("binance_publickey", null);
}
public String getBinancePrivateKey()
{
return settingPreferences.getString("binance_privatekey", null);
}
public boolean isBinanceActivated()
{
return settingPreferences.getBoolean("enable_binance", false);
}
public void disableBinance()
{
SharedPreferences.Editor editor = settingPreferences.edit();
editor.putBoolean("enable_binance", false);
editor.apply();
}
} }

View File

@ -8,11 +8,11 @@ import android.widget.ArrayAdapter;
import android.widget.Filter; import android.widget.Filter;
import android.widget.TextView; import android.widget.TextView;
import java.util.ArrayList;
import com.nauk.coinfolio.DataManagers.CurrencyData.Currency; import com.nauk.coinfolio.DataManagers.CurrencyData.Currency;
import com.nauk.coinfolio.R; import com.nauk.coinfolio.R;
import java.util.ArrayList;
/** /**
* Created by Guitoune on 17/01/2018. * Created by Guitoune on 17/01/2018.
*/ */
@ -46,9 +46,9 @@ public class CurrencyAdapter extends ArrayAdapter<Currency> {
} }
// Now assign alternate color for rows // Now assign alternate color for rows
if (position % 2 == 0) if (position % 2 == 0)
convertView.setBackgroundColor(context.getResources().getColor(R.color.listBackground));
else
convertView.setBackgroundColor(context.getResources().getColor(R.color.listBackground2)); convertView.setBackgroundColor(context.getResources().getColor(R.color.listBackground2));
else
convertView.setBackgroundColor(context.getResources().getColor(R.color.listBackground));
return convertView; return convertView;
} }
@ -70,13 +70,28 @@ public class CurrencyAdapter extends ArrayAdapter<Currency> {
if (constraint != null) { if (constraint != null) {
suggestions.clear(); suggestions.clear();
int i = 0;
int found = 0;
String temp = constraint.toString().toLowerCase(); String temp = constraint.toString().toLowerCase();
for (Currency currency : tempCurrency) {
while(i < tempCurrency.size() && found < 25)
{
Currency currency = tempCurrency.get(i);
if (currency.getName().toLowerCase().startsWith(temp)
|| currency.getSymbol().toLowerCase().startsWith(temp)) {
suggestions.add(currency);
found++;
}
i++;
}
/*for (Currency currency : tempCurrency) {
if (currency.getName().toLowerCase().startsWith(temp) if (currency.getName().toLowerCase().startsWith(temp)
|| currency.getSymbol().toLowerCase().startsWith(temp)) { || currency.getSymbol().toLowerCase().startsWith(temp)) {
suggestions.add(currency); suggestions.add(currency);
} }
} }*/
FilterResults filterResults = new FilterResults(); FilterResults filterResults = new FilterResults();
filterResults.values = suggestions; filterResults.values = suggestions;

View File

@ -1,20 +1,13 @@
package com.nauk.coinfolio.LayoutManagers; package com.nauk.coinfolio.LayoutManagers;
import android.animation.AnimatorInflater;
import android.animation.StateListAnimator;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.PorterDuff; import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffColorFilter;
import android.support.v7.widget.CardView;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import com.db.chart.model.ChartSet; import com.db.chart.model.ChartSet;
@ -26,18 +19,12 @@ import com.nauk.coinfolio.DataManagers.CurrencyData.Currency;
import com.nauk.coinfolio.DataManagers.CurrencyData.CurrencyDataChart; import com.nauk.coinfolio.DataManagers.CurrencyData.CurrencyDataChart;
import com.nauk.coinfolio.R; import com.nauk.coinfolio.R;
import org.w3c.dom.Text;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import static java.lang.Math.abs; import static java.lang.Math.abs;
import static java.lang.Math.floorDiv;
import static java.lang.Math.floorMod;
import static java.lang.Math.incrementExact;
import static java.sql.Types.NULL;
/** /**
* Created by Tiji on 05/01/2018. * Created by Tiji on 05/01/2018.

View File

@ -1,13 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient <gradient
android:angle="0" android:type="linear"
android:startColor="@color/colorPrimary" android:centerX="60%"
android:endColor="@color/colorAccent" android:startColor="#FF1CB5E0"
android:type="linear"/> android:centerColor="#FF000046"
android:endColor="#FF111124"
android:angle="90"/>
</shape> </shape>
</item>
</selector>

View File

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="@color/listBackground" android:fillColor="@color/buttonColor"
android:pathData="M3,4l9,16 9,-16L3,4zM6.38,6h11.25L12,16 6.38,6z"/> android:pathData="M3,4l9,16 9,-16L3,4zM6.38,6h11.25L12,16 6.38,6z"/>
</vector> </vector>

View File

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="@color/listBackground" android:fillColor="@color/buttonColor"
android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/> android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
</vector> </vector>

View File

@ -4,6 +4,6 @@
android:viewportWidth="24.0" android:viewportWidth="24.0"
android:viewportHeight="24.0"> android:viewportHeight="24.0">
<path <path
android:fillColor="@color/listBackground" android:fillColor="@color/buttonColor"
android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zM16.59,5.41L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z"/> android:pathData="M7.41,18.59L8.83,20 12,16.83 15.17,20l1.41,-1.41L12,14l-4.59,4.59zM16.59,5.41L15.17,4 12,7.17 8.83,4 7.41,5.41 12,10l4.59,-4.59z"/>
</vector> </vector>

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -10,22 +9,21 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@drawable/soft_gradient" android:background="@drawable/soft_gradient"
android:gravity="center"> android:gravity="center_horizontal"
android:orientation="vertical">
<!--<EditText <SearchView
android:layout_width="200dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/rounded_corners"/>-->
<AutoCompleteTextView
android:id="@+id/search_bar" android:id="@+id/search_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center|top" android:layout_gravity="center|top"
android:padding="15dp"
android:background="@color/listBackground"/> android:background="@color/listBackground"/>
<ListView
android:id="@+id/coinsPreview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout> </LinearLayout>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -44,8 +44,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay" app:popupTheme="@style/AppTheme.PopupOverlay">
android:background="@drawable/gradient_background">
</android.support.v7.widget.Toolbar> </android.support.v7.widget.Toolbar>
@ -61,7 +60,8 @@
android:id="@+id/switch_button" android:id="@+id/switch_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/quick_button"/> android:text="@string/quick_button"
android:visibility="visible"/>
</LinearLayout> </LinearLayout>
@ -77,7 +77,8 @@
android:id="@+id/settings_button" android:id="@+id/settings_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/action_settings" /> android:text="@string/action_settings"
android:visibility="visible"/>
</LinearLayout> </LinearLayout>
@ -86,7 +87,7 @@
<include layout="@layout/content_currency_summary" /> <include layout="@layout/content_currency_summary" />
<android.support.design.widget.BottomNavigationView <!--<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation_home" android:id="@+id/navigation_home"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -99,7 +100,7 @@
app:menu="@menu/navigation_home" app:menu="@menu/navigation_home"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:visibility="gone"/> android:visibility="gone"/>-->
<com.luseen.spacenavigation.SpaceNavigationView <com.luseen.spacenavigation.SpaceNavigationView
android:id="@+id/space" android:id="@+id/space"

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
@ -13,6 +12,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dp" android:layout_margin="10dp"
android:clickable="false" android:clickable="false"
android:backgroundTint="@color/listBackground2"
app:cardCornerRadius="8dp" app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintBottom_toBottomOf="parent">

View File

@ -4,11 +4,39 @@
android:id="@+id/swiperefresh" android:id="@+id/swiperefresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_marginBottom="50dp">
<ViewFlipper
android:id="@+id/viewFlipperSummary"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressBarWatchlist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleLarge"
android:layout_gravity="center"
android:background="@drawable/circular_progress_bar" />
<!--<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Watch list"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"/>-->
</LinearLayout>
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nestedScrollViewLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.nauk.coinfolio.Activities.HomeActivity" tools:context="com.nauk.coinfolio.Activities.HomeActivity"
@ -35,4 +63,35 @@
</android.support.v4.widget.NestedScrollView> </android.support.v4.widget.NestedScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Market cap"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"/>-->
<LinearLayout
android:id="@+id/layoutProgressMarketCap"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<ProgressBar
android:id="@+id/progressBarMarketCap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleLarge"
android:layout_gravity="center"
android:background="@drawable/circular_progress_bar" />
</LinearLayout>
</LinearLayout>
</ViewFlipper>
</android.support.v4.widget.SwipeRefreshLayout> </android.support.v4.widget.SwipeRefreshLayout>

View File

@ -1,4 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
<resources>
<color name="colorPrimary">#093e93</color>
<color name="colorPrimaryDark">#000046</color>
<color name="colorAccent">#1cb5e0</color>
<color name="mainTextViewColor">#FFFFFFFF</color>
<color name="secondaryTextViewColor">#FFDDDDDD</color>
<color name="transparent">#00000000</color>
<color name="decrease">#FFED143D</color>
<color name="increase">#FF00E000</color>
<color name="buttonColor">#FFFFFFFF</color>
<color name="listBackground">#000046</color>
<color name="listBackground2">#000046</color>
<color name="separationLine">#FF999999</color>
<color name="red">#FFF44336</color>
<color name="green">#FF4CAF50</color>
</resources>
-->
<resources> <resources>
<color name="colorPrimary">#000046</color> <color name="colorPrimary">#000046</color>
<color name="colorPrimaryDark">#111124</color> <color name="colorPrimaryDark">#111124</color>
@ -11,6 +30,7 @@
<color name="listBackground">#FFEEEEEE</color> <color name="listBackground">#FFEEEEEE</color>
<color name="listBackground2">#FFFFFFFF</color> <color name="listBackground2">#FFFFFFFF</color>
<color name="separationLine">#FF999999</color> <color name="separationLine">#FF999999</color>
<color name="buttonColor">#FFFFFFFF</color>
<color name="red">#FFF44336</color> <color name="red">#FFF44336</color>
<color name="green">#FF4CAF50</color> <color name="green">#FF4CAF50</color>
</resources> </resources>

View File

@ -59,7 +59,7 @@
<PreferenceCategory <PreferenceCategory
android:title="@string/pref_title_exchange_binance" android:title="@string/pref_title_exchange_binance"
android:enabled="false"> android:enabled="true">
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="enable_binance" android:key="enable_binance"
@ -79,7 +79,7 @@
android:capitalize="words" android:capitalize="words"
android:dependency="enable_binance" android:dependency="enable_binance"
android:inputType="text" android:inputType="text"
android:key="binance_privatekey" android:key="binance_publickey"
android:maxLines="1" android:maxLines="1"
android:selectAllOnFocus="true" android:selectAllOnFocus="true"
android:singleLine="true" android:singleLine="true"

View File

@ -2,7 +2,7 @@ package com.nauk.coinfolio;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
/** /**
* Example local unit test, which will execute on the development machine (host). * Example local unit test, which will execute on the development machine (host).

View File

@ -10,6 +10,7 @@
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m org.gradle.jvmargs=-Xmx1536m
android.enableD8=true
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit

View File

@ -1,131 +0,0 @@
*.class
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
# https://github.com/github/gitignore/blob/master/Scala.gitignore
*.class
*.log
# sbt specific
.cache
.history
.lib/
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/plugins/project/
# Scala-IDE specific
.scala_dependencies
.worksheet
**/.cache-main
# https://github.com/github/gitignore/blob/master/Global/Eclipse.gitignore
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# Eclipse Core
.project
.classpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# JDT-specific (Eclipse Java Development Tools)
**/.classpath
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# https://github.com/github/gitignore/blob/master/Global/JetBrains.gitignore
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# IntelliJ
.idea
*.iml
# Mongo Explorer plugin:
.idea/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# https://github.com/github/gitignore/blob/master/Maven.gitignore
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# test
test-output

View File

@ -1,92 +0,0 @@
# Contributing
When contributing to this repository, please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
Please note we have a code of conduct, please follow it in all your interactions with the project.
## Pull Request Process
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer to merge it for you.
## Code of Conduct
### Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
### Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
### Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
### Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
### Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
### Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@ -1,107 +0,0 @@
# Java client for [Binance API](https://www.binance.com/restapipub.html)
## Synopsis
Client for accessing Binance API using Java. An [async](src/main/java/com/github/johnsiu/binance/httpclients/AsyncBinanceClient.java) and a [sync](src/main/java/com/github/johnsiu/binance/httpclients/BinanceClient.java) versions of the client are available.
## Installation
Add the bintray repo to the pom of your maven project:
```xml
<repositories>
<repository>
<id>bintray-johnsiu-maven-repo</id>
<url>https://dl.bintray.com/johnsiu/maven-repo</url>
</repository>
</repositories>
```
then, add the dependency:
```xml
<dependency>
<groupId>com.github.johnsiu</groupId>
<artifactId>binance-java-client</artifactId>
<version>1.0.1</version>
</dependency>
```
## Usage
### Creating an instance of the async client using Guice
```java
Injector injector = Guice.createInjector(new BinanceClientModule());
AsyncBinanceClient asyncClient = injector.getInstance(AsyncBinanceClient.class);
```
### Creating an instance of the sync client using Guice
```java
Injector injector = Guice.createInjector(new BinanceClientModule());
BinanceClient client = injector.getInstance(BinanceClient.class);
```
### Getting latest price of a symbol
```java
Ticker ticker = client.getTicker("ETHBTC"));
double price = ticker.getLastPrice();
```
### Getting depth of a symbol
```java
Depth depth = client.getDepth("ETHBTC"));
```
### Placing a LIMIT order
```java
Keys keys = new Keys("YOUR_API_KEY", "YOUR_SECRET_KEY");
double quantity = 1;
double price = 0.020041;
Order order = client.placeLimitOrder(keys, "MCOETH", OrderSide.BUY, TimeInForce.GTC, quantity, price);
```
### Placing a MARKET order
```java
double quantity = 1;
Order order = client.placeMarketOrder(keys, "MCOETH", OrderSide.SELL, quantity);
```
### Checking an orders status
```java
OrderStatus orderStatus = client.checkOrderStatus(keys, order);
```
### Cancelling an order
```java
CancelOrder cancelOrder = client.cancelOrder(keys, order);
// or
CancelOrder cancelOrder = client.cancelOrder(keys, orderStatus);
```
### Getting a list of open orders
```java
List<OrderStatus> openOrders = client.getOpenOrders(keys, "MCOETH");
```
### Getting a list of current position
```java
Account account = client.getAccount(keys);
Map<String, Balance> balances = account.getBalances();
```
### Exception handling
```java
try {
Depth depth = client.getDepth("invalid symbol"));
} catch (ClientErrorException e) {
int httpStatusCode = e.getHttpStatusCode();
String errorCode = e.getErrorDetails().getCode();
String errorMessage = e.getErrorDetails().getMsg();
}
```
## Contributing
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
## Versioning
We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/johnsiu/binance-java-client/tags).
## License
This project is released into the public domain - see the [UNLICENSE](UNLICENSE) file for details.

View File

@ -1,24 +0,0 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

View File

@ -1,83 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.johnsiu</groupId>
<artifactId>binance-java-client</artifactId>
<version>1.0.1</version>
<properties>
<jackson-version>2.9.1</jackson-version>
</properties>
<distributionManagement>
<repository>
<id>bintray-repo-binance-java-client</id>
<url>https://api.bintray.com/maven/johnsiu/maven-repo/binance-java-client/;publish=1</url>
</repository>
</distributionManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.asynchttpclient</groupId>
<artifactId>async-http-client</artifactId>
<version>2.0.37</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.1-jre</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson-version}</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -1,29 +0,0 @@
package com.github.johnsiu.binance.deser;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.github.johnsiu.binance.models.Account.Balance;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import java.io.IOException;
import java.util.List;
import java.util.Map;
/**
* Jackson deserializer for turning an array of balances into a map of asset to balance.
*/
public class BalanceMapDeserializer extends JsonDeserializer<Map<String, Balance>> {
@Override
public Map<String, Balance> deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
List<Balance> balances = p.readValueAs(new TypeReference<List<Balance>>() {
});
Builder<String, Balance> builder = ImmutableMap.<String, Balance>builder();
balances.forEach(balance -> builder.put(balance.getAsset(), balance));
return builder.build();
}
}

View File

@ -1,27 +0,0 @@
package com.github.johnsiu.binance.exceptions;
import com.github.johnsiu.binance.models.ErrorDetails;
/**
* Exception thrown when Binance API returned an HTTP 4xx client error.
*/
public class ClientErrorException extends RuntimeException {
private int httpStatusCode;
// error message from Binance API.
private ErrorDetails errorDetails;
public ClientErrorException(int httpStatusCode, ErrorDetails errorDetails) {
super(errorDetails.getMsg());
this.httpStatusCode = httpStatusCode;
this.errorDetails = errorDetails;
}
public int getHttpStatusCode() {
return httpStatusCode;
}
public ErrorDetails getErrorDetails() {
return errorDetails;
}
}

View File

@ -1,69 +0,0 @@
package com.github.johnsiu.binance.httpclients;
import com.github.johnsiu.binance.models.Account;
import com.github.johnsiu.binance.models.CancelOrder;
import com.github.johnsiu.binance.models.Depth;
import com.github.johnsiu.binance.models.Keys;
import com.github.johnsiu.binance.models.Order;
import com.github.johnsiu.binance.models.Order.OrderSide;
import com.github.johnsiu.binance.models.Order.TimeInForce;
import com.github.johnsiu.binance.models.OrderStatus;
import com.github.johnsiu.binance.models.Ticker;
import com.google.inject.ImplementedBy;
import java.util.List;
import java.util.concurrent.CompletionStage;
/**
* Async client for accessing Binance API.
*/
@ImplementedBy(AsyncBinanceClientImpl.class)
public interface AsyncBinanceClient {
/**
* Get the ticker given the symbol.
*/
CompletionStage<Ticker> getTicker(String symbol);
/**
* Get the depth given the symbol. Limit defaults to 100.
*/
CompletionStage<Depth> getDepth(String symbol);
/**
* Get the depth given the symbol and limit.
*/
CompletionStage<Depth> getDepth(String symbol, int limit);
/**
* Place a LIMIT order.
*/
CompletionStage<Order> placeLimitOrder(Keys keys, String symbol, OrderSide side,
TimeInForce timeInForce, double quantity, double price);
/**
* Place a MARKET order.
*/
CompletionStage<Order> placeMarketOrder(Keys keys, String symbol, OrderSide side,
double quantity);
/**
* Check the status of an order.
*/
CompletionStage<OrderStatus> checkOrderStatus(Keys keys, Order order);
/**
* Cancel an order.
*/
CompletionStage<CancelOrder> cancelOrder(Keys keys, Order order);
/**
* Get all open orders.
*/
CompletionStage<List<OrderStatus>> getOpenOrders(Keys keys, String symbol);
/**
* Get account info.
*/
CompletionStage<Account> getAccount(Keys keys);
}

View File

@ -1,223 +0,0 @@
package com.github.johnsiu.binance.httpclients;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.johnsiu.binance.exceptions.ClientErrorException;
import com.github.johnsiu.binance.inject.BinanceClientModule;
import com.github.johnsiu.binance.models.Account;
import com.github.johnsiu.binance.models.CancelOrder;
import com.github.johnsiu.binance.models.Depth;
import com.github.johnsiu.binance.models.ErrorDetails;
import com.github.johnsiu.binance.models.Keys;
import com.github.johnsiu.binance.models.Order;
import com.github.johnsiu.binance.models.Order.OrderSide;
import com.github.johnsiu.binance.models.Order.OrderType;
import com.github.johnsiu.binance.models.Order.TimeInForce;
import com.github.johnsiu.binance.models.OrderStatus;
import com.github.johnsiu.binance.models.Ticker;
import io.netty.handler.codec.http.HttpMethod;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.inject.Named;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.BoundRequestBuilder;
import org.asynchttpclient.Response;
/**
* Concrete implementation of {@link AsyncBinanceClient}.
*/
public class AsyncBinanceClientImpl implements AsyncBinanceClient {
private static final String HMAC_SHA256 = "HmacSHA256";
private static final String APIKEY_HEADER = "X-MBX-APIKEY";
private static final String ORDER_ENDPOINT = "order";
private static final String OPEN_ORDERS_ENDPOINT = "openOrders";
private static final String ACCOUNT_ENDPOINT = "account";
private final String API_URL = "https://www.binance.com/api/v1/";
private final String API_V3_URL = "https://www.binance.com/api/v3/";
private final AsyncHttpClient asyncHttpClient;
private final ObjectMapper objectMapper;
@Inject
public AsyncBinanceClientImpl(AsyncHttpClient asyncHttpClient,
@Named(BinanceClientModule.BINANCE_CLIENT) ObjectMapper objectMapper) {
this.asyncHttpClient = asyncHttpClient;
this.objectMapper = objectMapper;
}
@Override
public CompletionStage<Ticker> getTicker(String symbol) {
return handleResponse(
asyncHttpClient.prepareGet(API_URL + "ticker/24hr?symbol=" + symbol).execute()
.toCompletableFuture(), new TypeReference<Ticker>() {
});
}
@Override
public CompletionStage<Depth> getDepth(String symbol) {
return getDepth(symbol, 100);
}
@Override
public CompletionStage<Depth> getDepth(String symbol, int limit) {
return handleResponse(asyncHttpClient.prepareGet(API_URL + "depth?symbol=" + symbol).execute()
.toCompletableFuture(), new TypeReference<Depth>() {
});
}
@Override
public CompletionStage<Order> placeLimitOrder(Keys keys, String symbol, OrderSide side,
TimeInForce timeInForce, double quantity, double price) {
String queryStr = String.format(
"symbol=%s&side=%s&type=%s&timeInForce=%s&quantity=%f&price=%f&timestamp=%d",
symbol, side.name(), OrderType.LIMIT, timeInForce.name(), quantity, price,
System.currentTimeMillis());
return makeSignedRequest(keys, HttpMethod.POST, ORDER_ENDPOINT, queryStr,
new TypeReference<Order>() {
});
}
@Override
public CompletionStage<Order> placeMarketOrder(Keys keys, String symbol, OrderSide side,
double quantity) {
String queryStr = String.format(
"symbol=%s&side=%s&type=%s&quantity=%f&timestamp=%d",
symbol, side.name(), OrderType.MARKET, quantity,
System.currentTimeMillis());
return makeSignedRequest(keys, HttpMethod.POST, ORDER_ENDPOINT, queryStr,
new TypeReference<Order>() {
});
}
@Override
public CompletionStage<OrderStatus> checkOrderStatus(Keys keys, Order order) {
String queryStr = String.format(
"symbol=%s&orderId=%d&origClientOrderId=%s&timestamp=%d",
order.getSymbol(), order.getOrderId(), order.getClientOrderId(),
System.currentTimeMillis());
return makeSignedRequest(keys, HttpMethod.GET, ORDER_ENDPOINT, queryStr,
new TypeReference<OrderStatus>() {
});
}
@Override
public CompletionStage<CancelOrder> cancelOrder(Keys keys, Order order) {
String queryStr = String.format(
"symbol=%s&orderId=%d&origClientOrderId=%s&timestamp=%d",
order.getSymbol(), order.getOrderId(), order.getClientOrderId(),
System.currentTimeMillis());
return makeSignedRequest(keys, HttpMethod.DELETE, ORDER_ENDPOINT, queryStr,
new TypeReference<CancelOrder>() {
});
}
@Override
public CompletionStage<List<OrderStatus>> getOpenOrders(Keys keys, String symbol) {
String queryStr = String.format(
"symbol=%s&timestamp=%d", symbol, System.currentTimeMillis());
return makeSignedRequest(keys, HttpMethod.GET, OPEN_ORDERS_ENDPOINT, queryStr,
new TypeReference<List<OrderStatus>>() {
}
);
}
@Override
public CompletionStage<Account> getAccount(Keys keys) {
String queryStr = String.format(
"timestamp=%d", System.currentTimeMillis());
return makeSignedRequest(keys, HttpMethod.GET, ACCOUNT_ENDPOINT, queryStr,
new TypeReference<Account>() {
}
);
}
private <T> CompletionStage<T> makeSignedRequest(Keys keys, HttpMethod method, String endpoint,
String queryStr, TypeReference<T> typeReference) {
return CompletableFuture.supplyAsync(() -> {
try {
String signature = signQueryString(keys, queryStr);
String url = API_V3_URL + endpoint + "?" + queryStr + "&signature=" + signature;
BoundRequestBuilder boundRequestBuilder;
if (method == HttpMethod.GET) {
boundRequestBuilder = asyncHttpClient.prepareGet(url);
} else if (method == HttpMethod.POST) {
boundRequestBuilder = asyncHttpClient.preparePost(url);
} else if (method == HttpMethod.DELETE) {
boundRequestBuilder = asyncHttpClient.prepareDelete(url);
} else {
throw new IllegalArgumentException("Unsupported method: " + method);
}
return handleResponse(boundRequestBuilder
.addHeader(
APIKEY_HEADER, keys.getApiKey())
.execute().toCompletableFuture(), typeReference);
} catch (UnsupportedEncodingException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new CompletionException(e);
}
}).thenCompose(Function.identity());
}
private String signQueryString(Keys keys, String queryString)
throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
byte[] byteKey = keys.getSecretKey().getBytes("UTF-8");
Mac sha256HMAC = Mac.getInstance(HMAC_SHA256);
sha256HMAC.init(new SecretKeySpec(byteKey, HMAC_SHA256));
return bytesToHex(sha256HMAC.doFinal(queryString.getBytes("UTF-8")));
}
private <T> CompletionStage<T> handleResponse(CompletableFuture<Response> responseFuture,
TypeReference<T> typeReference) {
return responseFuture.thenApply(response -> {
try {
if (response.getStatusCode() >= 400) {
throw new ClientErrorException(response.getStatusCode(),
objectMapper.readValue(response.getResponseBody(), ErrorDetails.class));
}
return objectMapper.readValue(response.getResponseBody(), typeReference);
} catch (IOException e) {
throw new CompletionException(e);
}
});
}
final private static char[] hexArray = "0123456789ABCDEF"
.toCharArray();
private String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
}

View File

@ -1,68 +0,0 @@
package com.github.johnsiu.binance.httpclients;
import com.github.johnsiu.binance.models.Account;
import com.github.johnsiu.binance.models.CancelOrder;
import com.github.johnsiu.binance.models.Depth;
import com.github.johnsiu.binance.models.Keys;
import com.github.johnsiu.binance.models.Order;
import com.github.johnsiu.binance.models.Order.OrderSide;
import com.github.johnsiu.binance.models.Order.TimeInForce;
import com.github.johnsiu.binance.models.OrderStatus;
import com.github.johnsiu.binance.models.Ticker;
import com.google.inject.ImplementedBy;
import java.util.List;
/**
* Client for accessing Binance API.
*/
@ImplementedBy(BinanceClientImpl.class)
public interface BinanceClient {
/**
* Get the ticker given the symbol.
*/
Ticker getTicker(String symbol);
/**
* Get the depth given the symbol. Limit defaults to 100.
*/
Depth getDepth(String symbol);
/**
* Get the depth given the symbol and limit.
*/
Depth getDepth(String symbol, int limit);
/**
* Place a LIMIT order.
*/
Order placeLimitOrder(Keys keys, String symbol, OrderSide side,
TimeInForce timeInForce, double quantity, double price);
/**
* Place a MARKET order.
*/
Order placeMarketOrder(Keys keys, String symbol, OrderSide side,
double quantity);
/**
* Check the status of an order.
*/
OrderStatus checkOrderStatus(Keys keys, Order order);
/**
* Cancel an order.
*/
CancelOrder cancelOrder(Keys keys, Order order);
/**
* Get all open orders.
*/
List<OrderStatus> getOpenOrders(Keys keys, String symbol);
/**
* Get account info.
*/
Account getAccount(Keys keys);
}

View File

@ -1,90 +0,0 @@
package com.github.johnsiu.binance.httpclients;
import com.github.johnsiu.binance.models.Account;
import com.github.johnsiu.binance.models.CancelOrder;
import com.github.johnsiu.binance.models.Depth;
import com.github.johnsiu.binance.models.Keys;
import com.github.johnsiu.binance.models.Order;
import com.github.johnsiu.binance.models.Order.OrderSide;
import com.github.johnsiu.binance.models.Order.TimeInForce;
import com.github.johnsiu.binance.models.OrderStatus;
import com.github.johnsiu.binance.models.Ticker;
import java.util.List;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import javax.inject.Inject;
/**
* Concrete implementation of {@link BinanceClient}.
*/
public class BinanceClientImpl implements BinanceClient {
private final AsyncBinanceClient asyncBinanceClient;
@Inject
public BinanceClientImpl(AsyncBinanceClient asyncBinanceClient) {
this.asyncBinanceClient = asyncBinanceClient;
}
@Override
public Ticker getTicker(String symbol) {
return joinAsync(asyncBinanceClient.getTicker(symbol));
}
@Override
public Depth getDepth(String symbol) {
return joinAsync(asyncBinanceClient.getDepth(symbol));
}
@Override
public Depth getDepth(String symbol, int limit) {
return joinAsync(asyncBinanceClient.getDepth(symbol, limit));
}
@Override
public Order placeLimitOrder(Keys keys, String symbol, OrderSide side,
TimeInForce timeInForce, double quantity, double price) {
return joinAsync(
asyncBinanceClient.placeLimitOrder(keys, symbol, side, timeInForce, quantity, price)
);
}
@Override
public Order placeMarketOrder(Keys keys, String symbol, OrderSide side,
double quantity) {
return joinAsync(
asyncBinanceClient.placeMarketOrder(keys, symbol, side, quantity)
);
}
@Override
public OrderStatus checkOrderStatus(Keys keys, Order order) {
return joinAsync(asyncBinanceClient.checkOrderStatus(keys, order));
}
@Override
public CancelOrder cancelOrder(Keys keys, Order order) {
return joinAsync(asyncBinanceClient.cancelOrder(keys, order));
}
@Override
public List<OrderStatus> getOpenOrders(Keys keys, String symbol) {
return joinAsync(asyncBinanceClient.getOpenOrders(keys, symbol));
}
@Override
public Account getAccount(Keys keys) {
return joinAsync(asyncBinanceClient.getAccount(keys));
}
private <T> T joinAsync(CompletionStage<T> completionStage) {
try {
return completionStage.toCompletableFuture().join();
} catch (CompletionException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
}
throw new RuntimeException(e.getCause());
}
}
}

View File

@ -1,24 +0,0 @@
package com.github.johnsiu.binance.inject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.inject.AbstractModule;
import com.google.inject.name.Names;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClient;
/**
* Guice module for dependency injection.
*/
public class BinanceClientModule extends AbstractModule {
public static final String BINANCE_CLIENT = "BinanceClient";
@Override
protected void configure() {
ObjectMapper objectMapper = new ObjectMapper();
bind(ObjectMapper.class)
.annotatedWith(Names.named(BINANCE_CLIENT))
.toInstance(objectMapper);
bind(AsyncHttpClient.class).to(DefaultAsyncHttpClient.class);
}
}

View File

@ -1,98 +0,0 @@
package com.github.johnsiu.binance.models;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.Map;
import com.github.johnsiu.binance.deser.BalanceMapDeserializer;
/**
* Represents an account info.
*/
public class Account {
private int makerCommission;
private int takerCommission;
private int buyerCommission;
private int sellerCommission;
private boolean canTrade;
private boolean canWithdraw;
private boolean canDeposit;
// asset to Balance
private Map<String, Balance> balances;
public int getMakerCommission() {
return makerCommission;
}
public int getTakerCommission() {
return takerCommission;
}
public int getBuyerCommission() {
return buyerCommission;
}
public int getSellerCommission() {
return sellerCommission;
}
public boolean isCanTrade() {
return canTrade;
}
public boolean isCanWithdraw() {
return canWithdraw;
}
public boolean isCanDeposit() {
return canDeposit;
}
@JsonDeserialize(using = BalanceMapDeserializer.class)
public Map<String, Balance> getBalances() {
return balances;
}
@Override
public String toString() {
return "Account{" +
"makerCommission=" + makerCommission +
", takerCommission=" + takerCommission +
", buyerCommission=" + buyerCommission +
", sellerCommission=" + sellerCommission +
", canTrade=" + canTrade +
", canWithdraw=" + canWithdraw +
", canDeposit=" + canDeposit +
", balances=" + balances +
'}';
}
public static class Balance {
private String asset;
private double free;
private double locked;
public String getAsset() {
return asset;
}
public double getFree() {
return free;
}
public double getLocked() {
return locked;
}
@Override
public String toString() {
return "Balance{" +
"asset='" + asset + '\'' +
", free=" + free +
", locked=" + locked +
'}';
}
}
}

View File

@ -1,38 +0,0 @@
package com.github.johnsiu.binance.models;
/**
* Represents a cancel order.
*/
public class CancelOrder {
private String symbol;
private long orderId;
private String origClientOrderId;
private String clientOrderId;
public String getSymbol() {
return symbol;
}
public long getOrderId() {
return orderId;
}
public String getOrigClientOrderId() {
return origClientOrderId;
}
public String getClientOrderId() {
return clientOrderId;
}
@Override
public String toString() {
return "CancelOrder{" +
"symbol='" + symbol + '\'' +
", orderId=" + orderId +
", origClientOrderId='" + origClientOrderId + '\'' +
", clientOrderId='" + clientOrderId + '\'' +
'}';
}
}

View File

@ -1,61 +0,0 @@
package com.github.johnsiu.binance.models;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
/**
* Represents depth of a symbol.
*/
public class Depth {
private long lastUpdateId;
private List<PriceQuantity> bids;
private List<PriceQuantity> asks;
@JsonCreator
public Depth(@JsonProperty("lastUpdateId") long lastUpdateId, @JsonProperty("bids") List<?> bids,
@JsonProperty("asks") List<?> asks) {
this.lastUpdateId = lastUpdateId;
Function<List<?>, List<PriceQuantity>> buildOrdersFunc = (orders) -> {
Builder<PriceQuantity> ordersBuilder = ImmutableList.<PriceQuantity>builder();
if (orders != null && orders.size() >= 2) {
orders.forEach(value -> {
Iterator iterator = Collection.class.cast(value).iterator();
ordersBuilder.add(new PriceQuantity(Double.valueOf((String) iterator.next()),
Double.valueOf((String) iterator.next())));
});
}
return ordersBuilder.build();
};
this.bids = buildOrdersFunc.apply(bids);
this.asks = buildOrdersFunc.apply(asks);
}
public long getLastUpdateId() {
return lastUpdateId;
}
public List<PriceQuantity> getBids() {
return bids;
}
public List<PriceQuantity> getAsks() {
return asks;
}
@Override
public String toString() {
return "Depth{" +
"lastUpdateId=" + lastUpdateId +
", bids=" + bids +
", asks=" + asks +
'}';
}
}

View File

@ -1,26 +0,0 @@
package com.github.johnsiu.binance.models;
/**
* Represents an error from Binance API.
*/
public class ErrorDetails {
private long code;
private String msg;
public long getCode() {
return code;
}
public String getMsg() {
return msg;
}
@Override
public String toString() {
return "ErrorDetails{" +
"code=" + code +
", msg='" + msg + '\'' +
'}';
}
}

View File

@ -1,23 +0,0 @@
package com.github.johnsiu.binance.models;
/**
* Represents API key and secret key for accessing SIGNED endpoints.
*/
public class Keys {
private String apiKey;
private String secretKey;
public Keys(String apiKey, String secretKey) {
this.apiKey = apiKey;
this.secretKey = secretKey;
}
public String getApiKey() {
return apiKey;
}
public String getSecretKey() {
return secretKey;
}
}

View File

@ -1,55 +0,0 @@
package com.github.johnsiu.binance.models;
import java.util.Date;
/**
* Represents an Order. Can be cancelled.
*/
public class Order {
private String symbol;
private long orderId;
private String clientOrderId;
private Date transactTime;
public String getSymbol() {
return symbol;
}
public long getOrderId() {
return orderId;
}
public String getClientOrderId() {
return clientOrderId;
}
public Date getTransactTime() {
return transactTime;
}
@Override
public String toString() {
return "Order{" +
"symbol='" + symbol + '\'' +
", orderId=" + orderId +
", clientOrderId='" + clientOrderId + '\'' +
", transactTime=" + transactTime +
'}';
}
public enum OrderSide {
BUY, SELL
}
public enum OrderType {
LIMIT, MARKET
}
public enum TimeInForce {
// Good Till Cancel
GTC,
// Immediate or Cancel
IOC
}
}

View File

@ -1,86 +0,0 @@
package com.github.johnsiu.binance.models;
import java.util.Date;
/**
* Represents an order status. Can be cancelled.
*/
public class OrderStatus extends Order {
private String price;
private String origQty;
private String executedQty;
private Status status;
private TimeInForce timeInForce;
private OrderType type;
private OrderSide side;
private String stopPrice;
private String icebergQty;
private Date time;
public String getPrice() {
return price;
}
public String getOrigQty() {
return origQty;
}
public String getExecutedQty() {
return executedQty;
}
public Status getStatus() {
return status;
}
public TimeInForce getTimeInForce() {
return timeInForce;
}
public OrderType getType() {
return type;
}
public OrderSide getSide() {
return side;
}
public String getStopPrice() {
return stopPrice;
}
public String getIcebergQty() {
return icebergQty;
}
public Date getTime() {
return time;
}
@Override
public String toString() {
return "OrderStatus{" +
"price='" + price + '\'' +
", origQty='" + origQty + '\'' +
", executedQty='" + executedQty + '\'' +
", status=" + status +
", timeInForce=" + timeInForce +
", type=" + type +
", side=" + side +
", stopPrice='" + stopPrice + '\'' +
", icebergQty='" + icebergQty + '\'' +
", time=" + time +
"} " + super.toString();
}
public enum Status {
NEW,
PARTIALLY_FILLED,
FILLED,
CANCELED,
PENDING_CANCEL,
REJECTED,
EXPIRED
}
}

View File

@ -1,31 +0,0 @@
package com.github.johnsiu.binance.models;
/**
* Represents price and quantity of a bid or an ask.
*/
public class PriceQuantity {
private double price;
private double quantity;
public PriceQuantity(double price, double quantity) {
this.price = price;
this.quantity = quantity;
}
public double getPrice() {
return price;
}
public double getQuantity() {
return quantity;
}
@Override
public String toString() {
return "PriceQuantity{" +
"price=" + price +
", quantity=" + quantity +
'}';
}
}

View File

@ -1,137 +0,0 @@
package com.github.johnsiu.binance.models;
import java.util.Date;
/**
* Represents a ticker of a symbol.
*/
public class Ticker {
private double priceChange;
private double priceChangePercent;
private double weightedAvgPrice;
private double prevClosePrice;
private double lastPrice;
private double lastQty;
private double bidPrice;
private double bidQty;
private double askPrice;
private double askQty;
private double openPrice;
private double highPrice;
private double lowPrice;
private double volume;
private double quoteVolume;
private Date openTime;
private Date closeTime;
private long firstId;
private long lastId;
private long count;
public double getPriceChange() {
return priceChange;
}
public double getPriceChangePercent() {
return priceChangePercent;
}
public double getWeightedAvgPrice() {
return weightedAvgPrice;
}
public double getPrevClosePrice() {
return prevClosePrice;
}
public double getLastPrice() {
return lastPrice;
}
public double getLastQty() {
return lastQty;
}
public double getBidPrice() {
return bidPrice;
}
public double getBidQty() {
return bidQty;
}
public double getAskPrice() {
return askPrice;
}
public double getAskQty() {
return askQty;
}
public double getOpenPrice() {
return openPrice;
}
public double getHighPrice() {
return highPrice;
}
public double getLowPrice() {
return lowPrice;
}
public double getVolume() {
return volume;
}
public double getQuoteVolume() {
return quoteVolume;
}
public Date getOpenTime() {
return openTime;
}
public Date getCloseTime() {
return closeTime;
}
public long getFirstId() {
return firstId;
}
public long getLastId() {
return lastId;
}
public long getCount() {
return count;
}
@Override
public String toString() {
return "Ticker{" +
"priceChange=" + priceChange +
", priceChangePercent=" + priceChangePercent +
", weightedAvgPrice=" + weightedAvgPrice +
", prevClosePrice=" + prevClosePrice +
", lastPrice=" + lastPrice +
", lastQty=" + lastQty +
", bidPrice=" + bidPrice +
", bidQty=" + bidQty +
", askPrice=" + askPrice +
", askQty=" + askQty +
", openPrice=" + openPrice +
", highPrice=" + highPrice +
", lowPrice=" + lowPrice +
", volume=" + volume +
", quoteVolume=" + quoteVolume +
", openTime=" + openTime +
", closeTime=" + closeTime +
", firstId=" + firstId +
", lastId=" + lastId +
", count=" + count +
'}';
}
}

View File

@ -1,87 +0,0 @@
package com.github.johnsiu.binance.httpclients;
import com.github.johnsiu.binance.exceptions.ClientErrorException;
import com.github.johnsiu.binance.inject.BinanceClientModule;
import com.github.johnsiu.binance.models.Account;
import com.github.johnsiu.binance.models.Depth;
import com.github.johnsiu.binance.models.Keys;
import com.github.johnsiu.binance.models.Order;
import com.github.johnsiu.binance.models.Order.OrderSide;
import com.github.johnsiu.binance.models.Order.TimeInForce;
import com.github.johnsiu.binance.models.OrderStatus;
import com.google.inject.Guice;
import com.google.inject.Injector;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
/**
* Test case for {@link BinanceClientImpl}
*/
public class BinanceClientImplTest {
@Test
public void testGetTicker() {
Injector injector = Guice.createInjector(new BinanceClientModule());
BinanceClient client = injector.getInstance(BinanceClient.class);
Assert.assertNotNull(client.getTicker("ETHBTC"));
}
@Test
public void testGetDepth() {
Injector injector = Guice.createInjector(new BinanceClientModule());
BinanceClient client = injector.getInstance(BinanceClient.class);
Assert.assertNotNull(client.getDepth("ETHBTC"));
}
@Test
public void testGetDepthError() {
Injector injector = Guice.createInjector(new BinanceClientModule());
BinanceClient client = injector.getInstance(BinanceClient.class);
try {
Depth depth = client.getDepth("ETHBT");
Assert.fail();
} catch (Exception e) {
ClientErrorException cast = (ClientErrorException) e;
Assert.assertEquals("Invalid symbol.", cast.getErrorDetails().getMsg());
}
}
@Test
public void testOrder() {
Injector injector = Guice.createInjector(new BinanceClientModule());
BinanceClient client = injector.getInstance(BinanceClient.class);
Keys keys = new Keys(System.getProperty("apiKey"),
System.getProperty("secretKey"));
String symbol = "MCOETH";
Order order = client
.placeLimitOrder(keys, symbol, OrderSide.BUY, TimeInForce.GTC, 1, 0.020041);
Assert.assertNotNull(order);
OrderStatus orderStatus = client.checkOrderStatus(keys, order);
Assert.assertNotNull(orderStatus);
List<OrderStatus> openOrders = client.getOpenOrders(keys, symbol);
Assert.assertFalse(openOrders.isEmpty());
openOrders.forEach(openOrder -> client.cancelOrder(keys, openOrder));
openOrders = client.getOpenOrders(keys, symbol);
Assert.assertTrue(openOrders.isEmpty());
}
@Test
public void testAccount() {
Injector injector = Guice.createInjector(new BinanceClientModule());
BinanceClient client = injector.getInstance(BinanceClient.class);
Keys keys = new Keys(System.getProperty("apiKey"),
System.getProperty("secretKey"));
Account account = client.getAccount(keys);
Assert.assertNotNull(account);
}
}

BIN
libs/binance-api.jar Normal file

Binary file not shown.