/* * Copyright (C) 2014 AChep@xda <artemchep@gmail.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package com.achep.base.ui.fragments.dialogs; import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ProgressBar; import com.achep.acdisplay.R; import com.achep.base.AppHeap; import com.achep.base.billing.Bitcoin; import com.achep.base.billing.PayPal; import com.achep.base.billing.SkuUi; import com.achep.base.interfaces.IActivityBase; import com.achep.base.interfaces.IConfiguration; import com.achep.base.ui.adapters.BetterArrayAdapter; import com.achep.base.ui.widgets.HeaderGridView; import com.achep.base.ui.widgets.TextView; import com.achep.base.utils.CoinUtils; import com.achep.base.utils.RippleUtils; import com.achep.base.utils.ToastUtils; import com.achep.base.utils.ViewUtils; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import org.solovyev.android.checkout.ActivityCheckout; import org.solovyev.android.checkout.BillingRequests; import org.solovyev.android.checkout.Checkout; import org.solovyev.android.checkout.Inventory; import org.solovyev.android.checkout.ProductTypes; import org.solovyev.android.checkout.Purchase; import org.solovyev.android.checkout.RequestListener; import org.solovyev.android.checkout.ResponseCodes; import org.solovyev.android.checkout.Sku; import java.util.Comparator; import static com.achep.base.Build.DEBUG; /** * @author Artem Chepurnoy */ public class DonateDialog extends DialogFragment { private static final String TAG = "DonateFragment"; private static final int SCREEN_LOADING = 1; private static final int SCREEN_INVENTORY = 2; private static final int SCREEN_EMPTY_VIEW = 4; @NonNull private final InventoryLoadedListener mInventoryLoadedListener = new InventoryLoadedListener(); @NonNull private final PurchaseListener mPurchaseListener = new PurchaseListener(); private ActivityCheckout mCheckout; private SkusAdapter mAdapter; private Inventory mInventory; private ProgressBar mProgressBar; private TextView mEmptyView; @Override public void onAttach(Context context) { super.onAttach(context); if (context instanceof IActivityBase) { IActivityBase ma = (IActivityBase) context; mCheckout = ma.getCheckout(); if (mCheckout == null) { String message = "You must call #requestCheckout() on the activity before!"; throw new RuntimeException(message); } return; // don't crash } throw new RuntimeException("Host activity must be an instance of IActivityBase.class!"); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInventory = mCheckout.loadInventory(); } @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { MaterialDialog md = initDialog(); LayoutInflater inflater = (LayoutInflater) getActivity() .getSystemService(Context.LAYOUT_INFLATER_SERVICE); FrameLayout frameLayout = new FrameLayout(getActivity()); // // Init description message. TextView textView = (TextView) inflater.inflate(R.layout.dialog_message, frameLayout, false); textView.setText(R.string.donate_dialog_message); textView.setPadding(0, textView.getPaddingTop(), 0, textView.getPaddingBottom() / 2); // Init view with error view and progressbar-s. View phView = inflater.inflate(R.layout.dialog_donate_placeholder, frameLayout, false); mProgressBar = (ProgressBar) phView.findViewById(R.id.progress); mEmptyView = (TextView) phView.findViewById(R.id.empty); mEmptyView.setText(R.string.donate_billing_not_supported); assert md.getCustomView() != null; HeaderGridView gv = (HeaderGridView) md.getCustomView().findViewById(R.id.grid); gv.addHeaderView(textView, null, false); gv.addHeaderView(phView, null, false); gv.setAdapter(mAdapter = new SkusAdapter(getActivity(), R.layout.sku)); gv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { SkuUi skuUi = (SkuUi) parent.getAdapter().getItem(position); purchase(skuUi.sku); } }); return md; } @NonNull private MaterialDialog initDialog() { MaterialDialog.Builder builder = new MaterialDialog.Builder(getActivity()) .iconRes(R.drawable.ic_gift_white_24dp) .title(R.string.donate_dialog_title) .customView(R.layout.dialog_donate, false) .neutralText(R.string.close); final IConfiguration configuration = AppHeap.getInstance().getConfiguration(); final boolean hasApl = configuration.getBilling().hasAlternativePaymentMethods(); if (!hasApl) return builder.build(); final Bitcoin btc = new Bitcoin(); final PayPal pp = new PayPal(); MaterialDialog.SingleButtonCallback callback = new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog materialDialog, @NonNull DialogAction dialogAction) { if (dialogAction == DialogAction.POSITIVE) { startPaymentIntentWithWarningAlertDialog(CoinUtils.getPaymentIntent(btc)); } else if (dialogAction == DialogAction.NEGATIVE) { startPaymentIntentWithWarningAlertDialog(CoinUtils.getPaymentIntent(pp)); } else if (dialogAction == DialogAction.NEUTRAL) { dismiss(); } } }; return builder .positiveText(btc.getNameResource()) .negativeText(pp.getNameResource()) .onPositive(callback) .onNegative(callback) .onNeutral(callback) .autoDismiss(false) .build(); } @Override public void onStart() { super.onStart(); mCheckout.createPurchaseFlow(mPurchaseListener); reloadInventory(); } @Override public void onStop() { mCheckout.destroyPurchaseFlow(); super.onStop(); } /** * Shows a warning alert dialog to note, that those methods * may suck hard and nobody will care about it.<br/> * Starts an intent if user is agree with it. */ private void startPaymentIntentWithWarningAlertDialog(final Intent intent) { new MaterialDialog.Builder(getActivity()) .content(R.string.donate_alert_no_responsibility) .negativeText(android.R.string.cancel) .positiveText(android.R.string.ok) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog materialDialog, @NonNull DialogAction dialogAction) { try { startActivity(intent); dismiss(); // Dismiss main fragment } catch (ActivityNotFoundException e) { /* hell no */ } } }) .build() .show(); } private void refreshUi(int visibility) { ViewUtils.setVisible(mProgressBar, visibility == SCREEN_LOADING); ViewUtils.setVisible(mEmptyView, visibility == SCREEN_EMPTY_VIEW); } private void reloadInventory() { // Set `loading` state. refreshUi(SCREEN_LOADING); // Reload the inventory. mInventory .load() .whenLoaded(mInventoryLoadedListener); } private void purchase(@NonNull final Sku sku) { if (DEBUG) Log.d(TAG, "Purchasing " + sku.toString() + "..."); mCheckout.whenReady(new Checkout.ListenerAdapter() { @Override public void onReady(@NonNull BillingRequests requests) { requests.purchase(sku, null, mCheckout.getPurchaseFlow()); } }); } /** * @author Artem Chepurnoy */ private class InventoryLoadedListener implements Inventory.Listener { @Override public void onLoaded(@NonNull Inventory.Products products) { final Inventory.Product product = products.get(ProductTypes.IN_APP); mAdapter.setNotifyOnChange(false); mAdapter.clear(); if (product.supported) { for (Sku sku : product.getSkus()) { final Purchase purchase = product.getPurchaseInState(sku, Purchase.State.PURCHASED); final SkuUi skuUi = new MySkuUi(sku, purchase != null); mAdapter.add(skuUi); } // Sort items by prices. mAdapter.sort(new Comparator<SkuUi>() { @Override public int compare(@NonNull SkuUi l, @NonNull SkuUi r) { return (int) (l.sku.detailedPrice.amount - r.sku.detailedPrice.amount); } }); // Show the inventory. refreshUi(SCREEN_INVENTORY); } else refreshUi(SCREEN_EMPTY_VIEW); mAdapter.notifyDataSetChanged(); } } /** * @author Artem Chepurnoy */ private abstract class BaseRequestListener<T> implements RequestListener<T> { @Override public void onError(int response, @NonNull Exception e) { ToastUtils.showShort(getActivity(), e.getLocalizedMessage()); } } /** * @author Artem Chepurnoy */ private final class PurchaseListener extends BaseRequestListener<Purchase> { @Override public void onSuccess(@NonNull Purchase purchase) { onPurchased(false); } @Override public void onError(int response, @NonNull Exception e) { switch (response) { case ResponseCodes.ITEM_ALREADY_OWNED: onPurchased(true); break; default: super.onError(response, e); } } private void onPurchased(boolean alreadyOwned) { ToastUtils.showLong(getActivity(), R.string.donate_thanks); if (alreadyOwned) { // Nothing has changed, so we don't need // to reload the inventory. return; } reloadInventory(); } } /** * @author Artem Chepurnoy */ private static final class SkusAdapter extends BetterArrayAdapter<SkuUi> { /** * @author Artem Chepurnoy */ private static final class ViewHolder extends BetterArrayAdapter.ViewHolder { @NonNull private final android.widget.TextView description; @NonNull private final android.widget.TextView price; @NonNull private final android.widget.TextView currency; @NonNull private final ImageView done; public ViewHolder(@NonNull View view) { super(view); description = (android.widget.TextView) view.findViewById(R.id.description); View layout = view.findViewById(R.id.cost); price = (android.widget.TextView) layout.findViewById(R.id.price); currency = (android.widget.TextView) layout.findViewById(R.id.currency); done = (ImageView) layout.findViewById(R.id.done); } } public SkusAdapter(@NonNull Context context, @LayoutRes int layoutRes) { super(context, layoutRes); } @NonNull @Override public BetterArrayAdapter.ViewHolder onCreateViewHolder(@NonNull View view) { return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull BetterArrayAdapter.ViewHolder viewHolder, int i) { bindItem((ViewHolder) viewHolder, getItem(i)); } private static void bindItem(@NonNull ViewHolder holder, @NonNull SkuUi skuUi) { RippleUtils.makeFor(holder.view, true); holder.description.setText(skuUi.getDescription()); int visibility; if (skuUi.isPurchased()) { visibility = View.GONE; holder.done.setVisibility(View.VISIBLE); } else { visibility = View.VISIBLE; holder.price.setText(skuUi.getPriceAmount()); holder.currency.setText(skuUi.getPriceCurrency()); holder.done.setVisibility(View.GONE); } holder.price.setVisibility(visibility); holder.currency.setVisibility(visibility); } } /** * @author Artem Chepurnoy */ private static class MySkuUi extends SkuUi { public MySkuUi(@NonNull Sku sku, boolean isPurchased) { super(sku, isPurchased); } @NonNull @Override protected String onCreateDescription(@NonNull Sku sku) { /* * Those are highly app specific and should probably be * moved. */ String prefix = "donation_"; if (sku.id.startsWith(prefix)) { int[] data = new int[]{ 1, R.string.donation_1, 4, R.string.donation_4, 10, R.string.donation_10, 20, R.string.donation_20, 50, R.string.donation_50, 99, R.string.donation_99, }; int price = Integer.parseInt(sku.id.substring(prefix.length())); for (int i = 0; i < data.length; i += 2) { if (price == data[i]) { Context context = AppHeap.getContext(); return context.getString(data[i + 1]); } } } Log.wtf(TAG, "Alien sku found!"); return "Alien sku found!"; } } }