Verified Commit 4d8f072e authored by Sarath's avatar Sarath
Browse files

Voucher UI Update

parent e7179583
......@@ -147,21 +147,10 @@
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name" />
<activity
android:name=".VouchersActivity"
android:exported="true"
android:theme="@style/ui_2_theme" />
<activity
android:name=".network.NetworkDashboard"
android:exported="true"
android:label="@string/title_activity_network_dashboard"
android:parentActivityName=".home.BalanceActivity"
android:theme="@style/ui_2_theme"></activity>
<activity
android:name=".utxos.UTXOSActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true"
android:label="@string/unspent_outputs"
android:name=".vouchers.VouchersActivity"
android:label="Voucher"
android:theme="@style/ui_2_theme" />
<activity
android:name=".OpenDimeActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
......@@ -180,20 +169,7 @@
android:name=".paynym.ClaimPayNymActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name" />
<activity
android:name=".paynym.paynymDetails.PayNymDetailsActivity"
android:label=""
android:theme="@style/ui_2_theme" />
<activity
android:name=".paynym.addPaynym.AddPaynymActivity"
android:exported="true"
android:label="@string/add_new"
android:theme="@style/ui_2_theme" />
<activity
android:name=".paynym.PayNymHome"
android:exported="true"
android:label="@string/paynyms"
android:theme="@style/ui_2_theme" />
<activity
android:name=".ExodusActivity"
android:configChanges="keyboardHidden|orientation|screenSize" />
......
......@@ -51,6 +51,7 @@ import com.samourai.wallet.util.AppUtil;
import com.samourai.wallet.util.DecimalDigitsInputFilter;
import com.samourai.wallet.util.FormatsUtil;
import com.samourai.wallet.util.PrefsUtil;
import com.samourai.wallet.vouchers.VouchersActivity;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
......
package com.samourai.wallet;
package com.samourai.wallet.vouchers;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.support.constraint.Group;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Patterns;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.inputmethod.InputMethodManager;
......@@ -14,42 +19,39 @@ import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.samourai.wallet.tor.TorManager;
import com.samourai.wallet.R;
import com.samourai.wallet.util.AddressFactory;
import com.samourai.wallet.util.VouchersUtil;
import com.samourai.wallet.vouchers.providers.FBTCProvider;
import com.samourai.wallet.vouchers.providers.ValidateResponse;
import com.samourai.wallet.vouchers.providers.VoucherProvider;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.DecimalFormat;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
public class VouchersActivity extends AppCompatActivity {
private String fbtc = null;
private static int FBTC_VOCHER_SIZE = 12;
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private String addr84;
private boolean validVoucher = false;
private TextView log, voucherTextCount;
private TextView voucherTextCount, successResponseTextView;
private static final String TAG = "VouchersActivity";
private String quotationSecret;
private int quotationId;
private String email = null;
private String voucherCode;
private EditText editText1, editText2, editText3, editText4;
private EditText editText1, editText2, editText3, editText4, emailInput;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private Button redeemButton;
Group loaderGroup;
ValueAnimator buttonVisibilityAnimator = ValueAnimator.ofFloat(0.3f, 1);
private Group loaderGroup, successMessageGroup;
private VoucherProvider fbtcProvider;
private ValidateResponse validateResponse;
private CoordinatorLayout snackBarContainer;
private ValueAnimator buttonVisibilityAnimator = ValueAnimator.ofFloat(0.3f, 1);
final private DecimalFormat df = new DecimalFormat("#");
public VouchersActivity() {
}
@Override
......@@ -63,93 +65,49 @@ public class VouchersActivity extends AppCompatActivity {
editText2 = findViewById(R.id.redeem_edttext_2);
editText3 = findViewById(R.id.redeem_edttext_3);
editText4 = findViewById(R.id.redeem_edttext_4);
emailInput = findViewById(R.id.voucher_email_input);
editText1.addTextChangedListener(new GenericTextWatcher(editText2, editText1));
editText2.addTextChangedListener(new GenericTextWatcher(editText3, editText1));
editText3.addTextChangedListener(new GenericTextWatcher(editText4, editText2));
editText4.addTextChangedListener(new GenericTextWatcher(editText4, editText3));
editText1.addTextChangedListener(new SplitTextWatcher(editText2, editText1));
editText2.addTextChangedListener(new SplitTextWatcher(editText3, editText1));
editText3.addTextChangedListener(new SplitTextWatcher(editText4, editText2));
editText4.addTextChangedListener(new SplitTextWatcher(editText4, editText3));
editText1.addTextChangedListener(voucherCounter);
editText2.addTextChangedListener(voucherCounter);
editText3.addTextChangedListener(voucherCounter);
editText4.addTextChangedListener(voucherCounter);
successResponseTextView = findViewById(R.id.voucher_redeem_success_text);
redeemButton = findViewById(R.id.voucher_redeemButton);
successMessageGroup = findViewById(R.id.voucher_success_message_group);
loaderGroup = findViewById(R.id.redeem_loader_group);
snackBarContainer = findViewById(R.id.voucher_snackbar_container);
loaderGroup.setVisibility(View.GONE);
fbtcProvider = new FBTCProvider(getApplicationContext());
redeemButton.setEnabled(false);
redeemButton.setOnClickListener(view -> {
if (loaderGroup.getVisibility() == View.GONE)
loaderGroup.setVisibility(View.VISIBLE);
else
loaderGroup.setVisibility(View.GONE);
if (validateResponse == null) {
validate();
} else {
beginRedeem();
}
});
voucherTextCount = findViewById(R.id.vouchers_code_count);
//
// addr84 = AddressFactory.getInstance(getApplication()).getBIP84(AddressFactory.RECEIVE_CHAIN).getBech32AsString();
// ((TextView) findViewById(R.id.voucher_receive_add)).setText("Receive address: ".concat(addr84));
//
setSupportActionBar(findViewById(R.id.appbar_voucher));
if (getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
buttonVisibilityAnimator.addUpdateListener(valueAnimator1 -> {
redeemButton.setAlpha(valueAnimator1.getAnimatedFraction());
});
// fbtc = VouchersUtil.getInstance().getFastBitcoinsAPI();
// email = VouchersUtil.getInstance().getFastBitcoinsEmail();
//
// redeemButton = findViewById(R.id.redeem_button);
// log = findViewById(R.id.voucher_log);
// EditText edt = findViewById(R.id.redeem_edttext_1);
//
// redeemButton.setText("Check");
// redeemButton.setOnClickListener(view -> {
// if (!validVoucher) {
//
// Disposable disposable = check(edt.getText().toString())
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe((jsonObject, throwable) -> {
//
// if (jsonObject != null) {
// Snackbar.make(view, "Valid voucher", Snackbar.LENGTH_SHORT).show();
// redeemButton.setText("Redeem");
// log.setText(jsonObject.toString(2));
// voucherCode = edt.getText().toString().toUpperCase().trim();
// quotationSecret = jsonObject.getString("quotation_secret");
// quotationId = jsonObject.getInt("quotation_id");
// validVoucher = VouchersUtil.getInstance().isValidFastBitcoinsCode(voucherCode);
// } else {
// Snackbar.make(view, "Error : ".concat(throwable.getMessage()), Snackbar.LENGTH_SHORT).show();
// }
// });
// compositeDisposable.add(disposable);
//
// } else {
//
// Disposable disposable = redeem()
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe((jsonObject, throwable) -> {
//
// if (jsonObject != null) {
// Snackbar.make(view, "Success", Snackbar.LENGTH_SHORT).show();
// redeemButton.setText("Check");
// log.setText(jsonObject.toString(2));
// } else {
// Snackbar.make(view, "Error : ".concat(throwable.getMessage()), Snackbar.LENGTH_SHORT).show();
// }
// });
// compositeDisposable.add(disposable);
//
// }
//
// });
buttonVisibilityAnimator.addUpdateListener(valueAnimator1 -> redeemButton.setAlpha(valueAnimator1.getAnimatedFraction()));
df.setMinimumIntegerDigits(1);
df.setMinimumFractionDigits(8);
df.setMaximumFractionDigits(8);
}
private TextWatcher voucherCounter = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
......@@ -159,7 +117,9 @@ public class VouchersActivity extends AppCompatActivity {
if (len == FBTC_VOCHER_SIZE) {
redeemButton.setEnabled(true);
hideKeyboard(VouchersActivity.this);
if (Objects.requireNonNull(getCurrentFocus()).getId() == editText4.getId()) {
hideKeyboard(VouchersActivity.this);
}
buttonVisibilityAnimator.start();
} else {
buttonVisibilityAnimator.reverse();
......@@ -170,7 +130,6 @@ public class VouchersActivity extends AppCompatActivity {
@Override
public void afterTextChanged(Editable editable) {
}
};
......@@ -182,93 +141,97 @@ public class VouchersActivity extends AppCompatActivity {
return first.concat(second).concat(third).concat(fourth);
}
private Single<JSONObject> check(String voucher) {
String url = fbtc.concat("quote");
return Single.fromCallable(() -> {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS);
if (TorManager.getInstance(getApplication()).isConnected()) {
builder.proxy(TorManager.getInstance(this.getApplicationContext()).getProxy());
}
if (BuildConfig.DEBUG) {
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
}
JSONObject json = new JSONObject();
json.put("email_address", email);
json.put("code", voucher);
RequestBody body = RequestBody.create(JSON, json.toString());
Request rb = new Request.Builder().url(url)
.post(body)
.build();
Response response = builder.build().newCall(rb).execute();
if (response.body() != null) {
// LogUtil.info(TAG, "check: ".concat(response.body().string()));
return new JSONObject(response.body().string());
} else {
throw new JSONException("Invalid response");
}
});
private void enableVoucherInput(boolean enable) {
editText1.setEnabled(enable);
editText2.setEnabled(enable);
editText3.setEnabled(enable);
editText4.setEnabled(enable);
}
private Single<JSONObject> redeem() {
private void clearVoucherInput() {
editText1.setText("");
editText2.setText("");
editText3.setText("");
editText4.setText("");
}
String url = fbtc.concat("redeem");
private void validate() {
return Single.fromCallable(() -> {
if (voucherCode == null) {
validVoucher = false;
throw new Exception("Error invalid code");
}
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS);
if (TorManager.getInstance(getApplication()).isConnected()) {
builder.proxy(TorManager.getInstance(this.getApplicationContext()).getProxy());
}
if (BuildConfig.DEBUG) {
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
}
String email = emailInput.getText().toString();
String voucher = getEnteredVoucher();
JSONObject json = new JSONObject();
json.put("currency", "voucher");
json.put("email_address", email);
json.put("currency", "USD");
json.put("code", voucherCode);
json.put("quotation_id", quotationId);
json.put("quotation_secret", quotationSecret);
json.put("delivery_address", addr84);
if (!(!TextUtils.isEmpty(email) && Patterns.EMAIL_ADDRESS.matcher(email).matches())) {
emailInput.setError("Invalid email");
return;
}
RequestBody body = RequestBody.create(JSON, json.toString());
Request rb = new Request.Builder().url(url)
.post(body)
.build();
if (!VouchersUtil.getInstance().isValidFastBitcoinsCode(voucher)) {
showErrorSnackBar("Invalid Voucher Code");
return;
}
enableVoucherInput(false);
loaderGroup.setVisibility(View.VISIBLE);
Disposable disposable = fbtcProvider.validate(email, voucher)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((response, throwable) -> {
validateResponse = response;
loaderGroup.setVisibility(View.GONE);
enableVoucherInput(true);
if (validateResponse != null) {
redeemButton.setBackground(getDrawable(R.drawable.button_green));
redeemButton.setText("Redeem \n ".concat(df.format(validateResponse.getAmount().doubleValue() / 1e8)).concat(" BTC"));
} else {
String error = throwable.getMessage();
if (error.equals("invalid request")) {
error = "Invalid voucher";
}
showErrorSnackBar("Error : ".concat(error));
}
});
compositeDisposable.add(disposable);
}
Response response = builder.build().newCall(rb).execute();
private void beginRedeem() {
loaderGroup.setVisibility(View.VISIBLE);
String addr84 = AddressFactory.getInstance(getApplication()).getBIP84(AddressFactory.RECEIVE_CHAIN).getBech32AsString();
Disposable disposable = fbtcProvider.redeem(validateResponse, addr84)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((redeemSuccess, throwable) -> {
loaderGroup.setVisibility(View.GONE);
if(throwable !=null){
showErrorSnackBar(throwable.getMessage());
return;
}
if (redeemSuccess != null && redeemSuccess) {
clearVoucherInput();
loaderGroup.setVisibility(View.INVISIBLE);
successMessageGroup.setVisibility(View.VISIBLE);
successResponseTextView.setText("Voucher code accepted by ".concat(fbtcProvider.getProviderName()).concat(" Redemption now being processed."));
validateResponse = null;
} else {
showErrorSnackBar("Error : ".concat(throwable.getMessage()));
}
});
compositeDisposable.add(disposable);
if (response.body() != null) {
return new JSONObject(response.body().string());
} else {
throw new JSONException("Invalid response");
}
}
});
private void showErrorSnackBar(String message) {
Snackbar snackbar = Snackbar.make(snackBarContainer, message, Snackbar.LENGTH_SHORT);
snackbar.getView().setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.red));
snackbar.show();
}
public class GenericTextWatcher implements TextWatcher {
public class SplitTextWatcher implements TextWatcher {
private EditText etPrev;
private EditText etNext;
public GenericTextWatcher(EditText etNext, EditText etPrev) {
SplitTextWatcher(EditText etNext, EditText etPrev) {
this.etPrev = etPrev;
this.etNext = etNext;
}
......@@ -293,12 +256,12 @@ public class VouchersActivity extends AppCompatActivity {
public static void hideKeyboard(Activity activity) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
//Find the currently focused view, so we can grab the correct window token from it.
View view = activity.getCurrentFocus();
//If no view currently has focus, create a new one, just so we can grab a window token from it
if (view == null) {
view = new View(activity);
}
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}
package com.samourai.wallet.vouchers.providers;
import android.content.Context;
import com.samourai.wallet.BuildConfig;
import com.samourai.wallet.tor.TorManager;
import com.samourai.wallet.util.LogUtil;
import com.samourai.wallet.util.VouchersUtil;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.TimeUnit;
import io.reactivex.Single;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
public class FBTCProvider extends VoucherProvider {
private static final String TAG = "FBTCProvider";
private Context mContext;
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
public FBTCProvider(Context context) {
this.mContext = context;
}
@Override
public Single<ValidateResponse> validate(String email, String voucher) {
String url = VouchersUtil.getInstance().getFastBitcoinsAPI().concat("quote");
return Single.fromCallable(() -> {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS);
if (TorManager.getInstance(mContext).isConnected()) {
builder.proxy(TorManager.getInstance(mContext).getProxy());
}
if (BuildConfig.DEBUG) {
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
}
JSONObject json = new JSONObject();
json.put("email_address", email);
json.put("code", voucher);
RequestBody body = RequestBody.create(JSON, json.toString());
Request rb = new Request.Builder().url(url)
.post(body)
.build();
Response response = builder.build().newCall(rb).execute();
if (response.body() != null) {
String responseString = response.body().string();
LogUtil.info(TAG, "validate: ".concat(responseString));
JSONObject object = new JSONObject(responseString);
if (object.getInt("error") != 0) {
throw new Error(object.getString("error_message"));
} else {
ValidateResponse validateResponse = new ValidateResponse();
validateResponse.setAmount(object.getLong("satoshi_amount"));
LogUtil.info(TAG, "validate: ".concat(String.valueOf(object.getLong("satoshi_amount"))));
validateResponse.setQuotationId(object.getInt("quotation_id"));
validateResponse.setQuotationSecret(object.getString("quotation_secret"));
validateResponse.setVoucher(voucher);
validateResponse.setEmail(email);
return validateResponse;
}
} else {
throw new JSONException("Invalid response");
}
});
}
@Override
public Single<Boolean> redeem(ValidateResponse response,String address) {
String url = VouchersUtil.getInstance().getFastBitcoinsAPI().concat("redeem");
return Single.fromCallable(() -> {
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout(90, TimeUnit.SECONDS)
.readTimeout(90, TimeUnit.SECONDS);
if (TorManager.getInstance(mContext).isConnected()) {
builder.proxy(TorManager.getInstance(mContext).getProxy());
}
if (BuildConfig.DEBUG) {
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
}
JSONObject json = new JSONObject();
json.put("currency", "voucher");
json.put("email_address", response.getEmail());
json.put("currency", "USD");
json.put("value", 0);
json.put("code", response.getVoucher());
json.put("quotation_id", response.getQuotationId());
json.put("quotation_secret", response.getQuotationSecret());
json.put("delivery_address", address);
RequestBody body = RequestBody.create(JSON, json.toString());
Request rb = new Request.Builder().url(url)
.post(body)
.build();
Response requestResponse = builder.build().newCall(rb).execute();