package com.greenaddress.greenbits.ui;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.text.Editable;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.Theme;
import com.google.common.collect.Lists;
import com.greenaddress.greenbits.GaService;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.MonetaryFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public abstract class UI {
public static final int INVALID_RESOURCE_ID = 0;
public static final ArrayList<String> UNITS = Lists.newArrayList("BTC", "mBTC", "\u00B5BTC", "bits");
private static final String MICRO_BTC = "\u00B5BTC";
private static final MonetaryFormat BTC = new MonetaryFormat().shift(0).minDecimals(8).noCode();
private static final MonetaryFormat MBTC = new MonetaryFormat().shift(3).minDecimals(5).noCode();
private static final MonetaryFormat UBTC = new MonetaryFormat().shift(6).minDecimals(2).noCode();
private static final DecimalFormat mDecimalFmt = new DecimalFormat("#,###.########", DecimalFormatSymbols.getInstance(Locale.US));
// Class to unify cancel and dismiss handling */
private static class DialogCloseHandler implements DialogInterface.OnCancelListener,
DialogInterface.OnDismissListener {
private final Runnable mCallback;
private final boolean mCancelOnly;
public DialogCloseHandler(final Runnable callback, final boolean cancelOnly) {
mCallback = callback;
mCancelOnly = cancelOnly;
}
@Override
public void onCancel(final DialogInterface d) { mCallback.run(); }
@Override
public void onDismiss(final DialogInterface d) { if (!mCancelOnly) mCallback.run(); }
}
public static void setDialogCloseHandler(final Dialog d, final Runnable callback, final boolean cancelOnly) {
final DialogCloseHandler handler = new DialogCloseHandler(callback, cancelOnly);
d.setOnCancelListener(handler);
d.setOnDismissListener(handler);
}
public static void setDialogCloseHandler(final Dialog d, final Runnable callback) {
setDialogCloseHandler(d, callback, false);
}
private static boolean isEnterKeyDown(final KeyEvent e) {
return e != null && e.getAction() == KeyEvent.ACTION_DOWN &&
e.getKeyCode() == KeyEvent.KEYCODE_ENTER;
}
public static TextView.OnEditorActionListener getListenerRunOnEnter(final Runnable r) {
return new EditText.OnEditorActionListener() {
@Override
public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE ||
actionId == EditorInfo.IME_ACTION_SEARCH ||
actionId == EditorInfo.IME_ACTION_SEND ||
isEnterKeyDown(event)) {
if (event == null || !event.isShiftPressed()) {
r.run(); // The user is done typing.
return true; // Consume.
}
}
return false; // Pass on to other listeners.
}
};
}
public static MaterialDialog.Builder popup(final Activity a, final String title, final int pos, final int neg) {
final MaterialDialog.Builder b;
b = new MaterialDialog.Builder(a)
.title(title)
.titleColorRes(R.color.white)
.positiveColorRes(R.color.accent)
.negativeColorRes(R.color.accent)
.contentColorRes(R.color.white)
.theme(Theme.DARK);
if (pos != INVALID_RESOURCE_ID)
b.positiveText(pos);
if (neg != INVALID_RESOURCE_ID)
return b.negativeText(neg);
return b;
}
public static MaterialDialog.Builder popup(final Activity a, final int title, final int pos, final int neg) {
return popup(a, a.getString(title), pos, neg);
}
public static MaterialDialog.Builder popup(final Activity a, final int title, final int pos) {
return popup(a, title, pos, INVALID_RESOURCE_ID);
}
public static MaterialDialog.Builder popup(final Activity a, final String title) {
return popup(a, title, android.R.string.ok, android.R.string.cancel);
}
public static MaterialDialog.Builder popup(final Activity a, final int title) {
return popup(a, title, android.R.string.ok, android.R.string.cancel);
}
public static Map<String, String> getTwoFactorLookup(final Resources res) {
final List<String> localized = Arrays.asList(res.getStringArray(R.array.twoFactorChoices));
final List<String> methods = Arrays.asList(res.getStringArray(R.array.twoFactorMethods));
final Map<String, String> map = new HashMap<>();
for (int i = 0; i < localized.size(); i++)
map.put(methods.get(i), localized.get(i));
return map;
}
public static MaterialDialog popupTwoFactorChoice(final Activity a, final GaService service,
final boolean skip, final CB.Runnable1T<String> callback) {
final List<String> methods = skip ? null : service.getEnabledTwoFactorMethods();
if (skip || methods.size() <= 1) {
// Caller elected to skip, or no choices are available: don't prompt
a.runOnUiThread(new Runnable() {
public void run() {
callback.run((skip || methods.isEmpty()) ? null : methods.get(0));
}
});
return null;
}
// Return a pop up dialog to let the user choose.
final Map<String, String> localizedMap = getTwoFactorLookup(a.getResources());
final String[] localizedMethods = new String[methods.size()];
for (int i = 0; i < methods.size(); i++)
localizedMethods[i] = localizedMap.get(methods.get(i));
return popup(a, R.string.twoFactorChoicesTitle, R.string.choose, R.string.cancel)
.items(localizedMethods)
.itemsCallbackSingleChoice(0, new MaterialDialog.ListCallbackSingleChoice() {
@Override
public boolean onSelection(final MaterialDialog dialog, final View v, final int which, final CharSequence text) {
callback.run(methods.get(which));
return true;
}
}).build();
}
public static MaterialDialog popupWait(final Activity a, final int title) {
final int id = INVALID_RESOURCE_ID;
final MaterialDialog dialog = popup(a, title, id).progress(true, 0).build();
dialog.show();
return dialog;
}
public static void toast(final Activity activity, final int id, final int len) {
activity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(activity, id, len).show();
}
});
}
public static void toast(final Activity activity, final String s, final int len) {
activity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(activity, s, len).show();
}
});
}
public static void toast(final Activity activity, final Throwable t, final Button reenable) {
t.printStackTrace();
toast(activity, t.getMessage(), reenable);
}
public static void toast(final Activity activity, final String msg, final Button reenable) {
activity.runOnUiThread(new Runnable() {
public void run() {
if (reenable != null)
reenable.setEnabled(true);
Toast.makeText(activity, msg, Toast.LENGTH_LONG).show();
}
});
}
// Dummy TextWatcher for simple overrides
public static class TextWatcher implements android.text.TextWatcher {
TextWatcher() { super(); }
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { }
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { }
@Override
public void afterTextChanged(final Editable s) { }
}
public static View mapClick(final Activity activity, final int id, final View.OnClickListener fn) {
final View v = find(activity, id);
v.setOnClickListener(fn);
return v;
}
public static View mapClick(final Activity activity, final int id, final Intent activityIntent) {
return mapClick(activity, id, new View.OnClickListener() {
@Override
public void onClick(final View v) {
activity.startActivity(activityIntent);
}
});
}
public static void mapEnterToPositive(final Dialog dialog, final int editId) {
final TextView edit = UI.find(dialog, editId);
edit.setOnEditorActionListener(getListenerRunOnEnter(new Runnable() {
public void run() {
final MaterialDialog md = (MaterialDialog) dialog;
md.onClick(md.getActionButton(DialogAction.POSITIVE));
}
}));
}
// Show/Hide controls
public static void showIf(final boolean condition, final View... views) {
for (final View v: views)
v.setVisibility(condition ? View.VISIBLE : View.GONE);
}
public static void show(final View... views) { showIf(true, views); }
public static void hideIf(final boolean condition, final View... views) {
showIf(!condition, views);
}
public static void hide(final View... views) { showIf(false, views); }
// Enable/Disable controls
public static void enableIf(final boolean condition, final View... views) {
for (final View v: views)
v.setEnabled(condition);
}
public static void enable(final View... views) { enableIf(true, views); }
public static void disableIf(final boolean condition, final View... views) {
enableIf(!condition, views);
}
public static void disable(final View... views) { enableIf(false, views); }
public static String getText(final TextView text) {
return text.getText().toString();
}
public static void clear(final TextView... views) {
for (final TextView v: views)
v.setText(R.string.empty);
}
public static <T extends View> T find(final Activity activity, final int id) {
return (T) activity.findViewById(id);
}
public static <T extends View> T find(final View v, final int id) {
return (T) v.findViewById(id);
}
public static <T extends View> T find(final Dialog dialog, final int id) {
return (T) dialog.findViewById(id);
}
public static LinearLayout.LayoutParams getScreenLayout(final Activity activity,
final double scale) {
final DisplayMetrics dm = new DisplayMetrics();
activity.getWindowManager().getDefaultDisplay().getMetrics(dm);
final int min = (int) (Math.min(dm.heightPixels, dm.widthPixels) * scale);
return new LinearLayout.LayoutParams(min, min);
}
public static void showDialog(final Dialog dialog) {
// (FIXME not sure if there's any smaller subset of these 3 calls below which works too)
dialog.getWindow().clearFlags(LayoutParams.FLAG_NOT_FOCUSABLE |
LayoutParams.FLAG_ALT_FOCUSABLE_IM);
dialog.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_VISIBLE);
dialog.show();
}
private static int getUnitSymbol(final GaService service) {
final String unit = service.getBitcoinUnit();
if (MonetaryFormat.CODE_BTC.equals(unit))
return R.string.fa_btc_space;
if (MonetaryFormat.CODE_MBTC.equals(unit))
return R.string.fa_mbtc_space;
if (MICRO_BTC.equals(unit))
return R.string.fa_ubtc_space;
return R.string.fa_bits_space;
}
private static MonetaryFormat getUnitFormat(final GaService service) {
final String unit = service.getBitcoinUnit();
if (MonetaryFormat.CODE_BTC.equals(unit))
return BTC;
if (MonetaryFormat.CODE_MBTC.equals(unit))
return MBTC;
return UBTC;
}
public static String formatCoinValue(final GaService service, final Coin value) {
return getUnitFormat(service).format(value).toString();
}
public static Coin parseCoinValue(final GaService service, final String value) {
return getUnitFormat(service).parse(value);
}
public static String setCoinText(final GaService service,
final TextView symbol, final TextView amount,
final Coin value) {
if (symbol != null) {
if (GaService.IS_ELEMENTS)
symbol.setText(service.getAssetSymbol());
else
symbol.setText(getUnitSymbol(service));
}
if (value == null)
return null;
final String formatted = formatCoinValue(service, value);
if (amount != null)
amount.setText(formatted);
return formatted;
}
public static String setCoinText(final GaService service, final View v,
final int symbolId, final int amountId,
final Coin value) {
return setCoinText(service, (TextView) find(v, symbolId),
(TextView) find(v, amountId), value);
}
public static String setCoinText(final GaActivity activity,
final int symbolId, final int amountId,
final Coin value) {
return setCoinText(activity.mService, (TextView) find(activity, symbolId),
(TextView) find(activity, amountId), value);
}
public static void setAmountText(final TextView v, final String value) {
try {
v.setText(mDecimalFmt.format(Double.valueOf(value)));
} catch (final NumberFormatException e) {
v.setText(value);
}
}
}