convert purchase dialog

This commit is contained in:
Phillip Thelen 2017-11-07 12:10:09 +01:00
parent 9a9f0c5a28
commit 6fa2e25f93
5 changed files with 282 additions and 296 deletions

View file

@ -0,0 +1,5 @@
package com.habitrpg.android.habitica.events
import com.habitrpg.android.habitica.models.shops.ShopItem
class ShopItemPurchasedEvent(val item: ShopItem)

View file

@ -9,6 +9,7 @@ import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.events.ShopItemPurchasedEvent
import com.habitrpg.android.habitica.helpers.RemoteConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.shops.Shop
@ -19,6 +20,7 @@ import com.habitrpg.android.habitica.ui.adapter.inventory.ShopRecyclerAdapter
import com.habitrpg.android.habitica.ui.fragments.BaseFragment
import com.habitrpg.android.habitica.ui.helpers.SafeDefaultItemAnimator
import kotlinx.android.synthetic.main.fragment_recyclerview.*
import org.greenrobot.eventbus.Subscribe
import rx.functions.Action1
import javax.inject.Inject
@ -174,4 +176,9 @@ class ShopFragment : BaseFragment() {
private const val SHOP_IDENTIFIER_KEY = "SHOP_IDENTIFIER_KEY"
}
@Subscribe
fun onItemPurchased(event: ShopItemPurchasedEvent) {
loadShopInventory()
}
}

View file

@ -86,10 +86,13 @@ class ShopItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), Vi
}
override fun onClick(view: View) {
val dialog = PurchaseDialog(context, HabiticaBaseApplication.getComponent(), item)
dialog.shopIdentifier = shopIdentifier
dialog.setIsPinned(isPinned)
dialog.show()
val item = item
if (item != null) {
val dialog = PurchaseDialog(context, HabiticaBaseApplication.getComponent(), item)
dialog.shopIdentifier = shopIdentifier
dialog.isPinned = isPinned
dialog.show()
}
}
fun hidePinIndicator() {

View file

@ -1,292 +0,0 @@
package com.habitrpg.android.habitica.ui.views.shops;
import android.app.AlertDialog;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ScrollView;
import android.widget.TextView;
import com.habitrpg.android.habitica.R;
import com.habitrpg.android.habitica.components.AppComponent;
import com.habitrpg.android.habitica.data.InventoryRepository;
import com.habitrpg.android.habitica.data.UserRepository;
import com.habitrpg.android.habitica.events.ShowSnackbarEvent;
import com.habitrpg.android.habitica.events.commands.OpenGemPurchaseFragmentCommand;
import com.habitrpg.android.habitica.helpers.RemoteConfigManager;
import com.habitrpg.android.habitica.helpers.RxErrorHandler;
import com.habitrpg.android.habitica.models.shops.Shop;
import com.habitrpg.android.habitica.models.shops.ShopItem;
import com.habitrpg.android.habitica.models.user.User;
import com.habitrpg.android.habitica.ui.views.CurrencyView;
import com.habitrpg.android.habitica.ui.views.CurrencyViews;
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper;
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar;
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientCurrencyDialog;
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog;
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGoldDialog;
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog;
import org.greenrobot.eventbus.EventBus;
import java.util.Date;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import rx.Observable;
import rx.subscriptions.CompositeSubscription;
public class PurchaseDialog extends AlertDialog {
@Inject
UserRepository userRepository;
@Inject
InventoryRepository inventoryRepository;
@Inject
RemoteConfigManager configManager;
@BindView(R.id.currencyView)
CurrencyViews currencyView;
@BindView(R.id.limitedTextView)
TextView limitedTextView;
@BindView(R.id.priceLabel)
CurrencyView priceLabel;
@BindView(R.id.buyButton)
View buyButton;
@BindView(R.id.pinButton)
ImageButton pinButton;
@BindView(R.id.content_container)
ViewGroup contentContainer;
@BindView(R.id.scrollView)
ScrollView scrollView;
private ShopItem shopItem;
private CompositeSubscription compositeSubscription;
public String shopIdentifier;
private User user;
private boolean isPinned;
public PurchaseDialog(Context context, AppComponent component, ShopItem item) {
super(context);
component.inject(this);
compositeSubscription = new CompositeSubscription();
LayoutInflater inflater = LayoutInflater.from(context);
View view = inflater.inflate(R.layout.dialog_purchase_shopitem, null);
ButterKnife.bind(this, view);
setView(view);
setShopItem(item);
compositeSubscription.add(userRepository.getUser().subscribe(this::setUser, RxErrorHandler.handleEmptyError()));
if (!this.configManager.newShopsEnabled()) {
pinButton.setVisibility(View.GONE);
}
}
private void setUser(User user) {
this.user = user;
currencyView.setGold(user.getStats().getGp());
currencyView.setGems(user.getGemCount());
currencyView.setHourglasses(user.getHourglassCount());
if ("gems".equals(shopItem.getPurchaseType())) {
int gemsLeft = shopItem.getLimitedNumberLeft() != null ? shopItem.getLimitedNumberLeft() : 0;
int maxGems = user.getPurchased().getPlan().totalNumberOfGems();
if (maxGems > 0) {
limitedTextView.setText(getContext().getString(R.string.gems_left_max, gemsLeft, maxGems));
} else {
limitedTextView.setText(getContext().getString(R.string.gems_left_nomax, gemsLeft));
}
limitedTextView.setVisibility(View.VISIBLE);
limitedTextView.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.green_10));
}
if (shopItem != null && !shopItem.canAfford(user)) {
priceLabel.setCantAfford(true);
}
}
@Override
public void dismiss() {
userRepository.close();
inventoryRepository.close();
if (compositeSubscription != null && !compositeSubscription.isUnsubscribed()) {
compositeSubscription.unsubscribe();
}
super.dismiss();
}
private void setShopItem(ShopItem item) {
this.shopItem = item;
buyButton.setVisibility(View.VISIBLE);
if (item.getUnlockCondition() == null) {
priceLabel.setValue((double) item.getValue());
priceLabel.setCurrency(item.getCurrency());
} else {
setBuyButtonEnabled(false);
}
if (item.isLimited()) {
//TODO: replace with correct date once API is final
limitedTextView.setText(getContext().getString(R.string.available_until, new Date().toString()));
} else {
limitedTextView.setVisibility(View.GONE);
}
priceLabel.setLocked(item.getLocked());
PurchaseDialogContent contentView;
if (shopItem.isTypeItem()) {
contentView = new PurchaseDialogItemContent(getContext());
} else if (shopItem.isTypeQuest()) {
contentView = new PurchaseDialogQuestContent(getContext());
inventoryRepository.getQuestContent(item.getKey()).first().subscribe(((PurchaseDialogQuestContent) contentView)::setQuestContent, RxErrorHandler.handleEmptyError());
} else if (shopItem.isTypeGear()) {
contentView = new PurchaseDialogGearContent(getContext());
inventoryRepository.getEquipment(item.getKey()).first().subscribe(((PurchaseDialogGearContent) contentView)::setEquipment, RxErrorHandler.handleEmptyError());
} else if ("gems".equals(shopItem.getPurchaseType())) {
contentView = new PurchaseDialogGemsContent(getContext());
} else {
contentView = new PurchaseDialogBaseContent(getContext());
}
contentView.setItem(shopItem);
contentContainer.addView(contentView);
setScrollviewSize();
}
private void setScrollviewSize() {
scrollView.post(() -> {
if (getWindow() != null) {
int height = scrollView.getChildAt(0).getHeight();
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindow().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
int screenHeight = displayMetrics.heightPixels;
int spaceRequired = (int) (displayMetrics.density * 160);
if (height > screenHeight-spaceRequired) {
ViewGroup.LayoutParams myScrollViewParams = scrollView.getLayoutParams();
myScrollViewParams.height = screenHeight-spaceRequired;
scrollView.setLayoutParams(myScrollViewParams);
}
}
});
}
public void setIsPinned(boolean isPinned) {
this.isPinned = isPinned;
if (isPinned) {
pinButton.setImageBitmap(HabiticaIconsHelper.imageOfUnpinItem());
} else {
pinButton.setImageBitmap(HabiticaIconsHelper.imageOfPinItem());
}
}
@OnClick(R.id.closeButton)
void onCloseClicked() {
dismiss();
}
@OnClick(R.id.buyButton)
void onBuyButtonClicked() {
final String[] snackbarText = {""};
if (shopItem.isValid()) {
if (shopItem.getLocked()) {
return;
}
if (shopItem.canAfford(user)) {
Observable<Void> observable;
if ((shopIdentifier != null && shopIdentifier.equals(Shop.TIME_TRAVELERS_SHOP)) || "mystery_set".equals(shopItem.getPurchaseType())) {
if (shopItem.getPurchaseType().equals("gear")) {
observable = inventoryRepository.purchaseMysterySet(shopItem.getCategoryIdentifier());
} else {
observable = inventoryRepository.purchaseHourglassItem(shopItem.getPurchaseType(), shopItem.getKey());
}
} else if (shopItem.getPurchaseType().equals("quests") && shopItem.getCurrency().equals("gold")) {
observable = inventoryRepository.purchaseQuest(shopItem.getKey());
} else if ("gold".equals(shopItem.getCurrency()) && !"gem".equals(shopItem.getKey())) {
observable = inventoryRepository.buyItem(user, shopItem.getKey(), shopItem.getValue()).flatMap(buyResponse -> {
if (shopItem.getKey().equals("armoire")) {
if (buyResponse.armoire.get("type").equals("gear")) {
snackbarText[0] = getContext().getString(R.string.armoireEquipment, buyResponse.armoire.get("dropText"));
} else if (buyResponse.armoire.get("type").equals("food")) {
snackbarText[0] = getContext().getString(R.string.armoireFood, buyResponse.armoire.get("dropArticle"), buyResponse.armoire.get("dropText"));
} else {
snackbarText[0] = getContext().getString(R.string.armoireExp);
}
}
return Observable.just(null);
});
} else {
observable = inventoryRepository.purchaseItem(shopItem.getPurchaseType(), shopItem.getKey());
}
observable
.doOnNext(aVoid -> {
ShowSnackbarEvent event = new ShowSnackbarEvent();
event.title = getContext().getString(R.string.successful_purchase, shopItem.getText());
if (snackbarText[0].length() > 0) {
event.text = snackbarText[0];
}
event.type = HabiticaSnackbar.SnackbarDisplayType.NORMAL;
event.rightIcon = priceLabel.getCompoundDrawables()[0];
event.rightTextColor = priceLabel.getCurrentTextColor();
event.rightText = "-" + priceLabel.getText();
EventBus.getDefault().post(event);
})
.flatMap(buyResponse -> userRepository.retrieveUser(false, true))
.flatMap(user1 -> inventoryRepository.retrieveInAppRewards())
.subscribe(buyResponse -> {
}, throwable -> {
if (throwable.getClass().isAssignableFrom(retrofit2.HttpException.class)) {
retrofit2.HttpException error = (retrofit2.HttpException) throwable;
if (error.code() == 401 && shopItem.getCurrency().equals("gems")) {
EventBus.getDefault().post(new OpenGemPurchaseFragmentCommand());
}
}
});
} else {
InsufficientCurrencyDialog dialog = null;
if ("gold".equals(shopItem.getCurrency())) {
dialog = new InsufficientGoldDialog(getContext());
} else if ("gems".equals(shopItem.getCurrency())) {
dialog = new InsufficientGemsDialog(getContext());
} else if ("hourglasses".equals(shopItem.getCurrency())) {
dialog = new InsufficientHourglassesDialog(getContext());
}
if (dialog != null) {
dialog.show();
}
}
}
dismiss();
}
@OnClick(R.id.pinButton)
void onPinButtonClicked() {
inventoryRepository.togglePinnedItem(shopItem).subscribe(shopItems -> this.setIsPinned(!this.isPinned), RxErrorHandler.handleEmptyError());
}
private void setBuyButtonEnabled(boolean enabled) {
if (enabled) {
buyButton.setAlpha(0.5f);
} else{
buyButton.setAlpha(1.0f);
}
}
}

View file

@ -0,0 +1,263 @@
package com.habitrpg.android.habitica.ui.views.shops
import android.app.AlertDialog
import android.content.Context
import android.support.v4.content.ContextCompat
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ScrollView
import android.widget.TextView
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.AppComponent
import com.habitrpg.android.habitica.data.InventoryRepository
import com.habitrpg.android.habitica.data.UserRepository
import com.habitrpg.android.habitica.events.ShowSnackbarEvent
import com.habitrpg.android.habitica.events.commands.OpenGemPurchaseFragmentCommand
import com.habitrpg.android.habitica.helpers.RemoteConfigManager
import com.habitrpg.android.habitica.helpers.RxErrorHandler
import com.habitrpg.android.habitica.models.shops.Shop
import com.habitrpg.android.habitica.models.shops.ShopItem
import com.habitrpg.android.habitica.models.user.User
import com.habitrpg.android.habitica.ui.views.CurrencyView
import com.habitrpg.android.habitica.ui.views.CurrencyViews
import com.habitrpg.android.habitica.ui.views.HabiticaIconsHelper
import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGemsDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientGoldDialog
import com.habitrpg.android.habitica.ui.views.insufficientCurrency.InsufficientHourglassesDialog
import org.greenrobot.eventbus.EventBus
import java.util.Date
import javax.inject.Inject
import com.habitrpg.android.habitica.extensions.bindView
import com.habitrpg.android.habitica.models.inventory.Equipment
import com.habitrpg.android.habitica.models.inventory.QuestContent
import rx.Observable
import rx.functions.Action1
import rx.subscriptions.CompositeSubscription
class PurchaseDialog(context: Context, component: AppComponent, val item: ShopItem) : AlertDialog(context) {
@Inject
lateinit var userRepository: UserRepository
@Inject
lateinit var inventoryRepository: InventoryRepository
@Inject
lateinit var configManager: RemoteConfigManager
private val customView: View by lazy {
LayoutInflater.from(context).inflate(R.layout.dialog_purchase_shopitem, null)
}
private val currencyView: CurrencyViews by bindView(customView, R.id.currencyView)
private val limitedTextView: TextView by bindView(customView, R.id.limitedTextView)
private val priceLabel: CurrencyView by bindView(customView, R.id.priceLabel)
private val buyButton: View by bindView(customView, R.id.buyButton)
private val closeButton: View by bindView(customView, R.id.closeButton)
private val pinButton: ImageButton by bindView(customView, R.id.pinButton)
private val contentContainer: ViewGroup by bindView(customView, R.id.content_container)
private val scrollView: ScrollView by bindView(customView, R.id.scrollView)
var shopItem: ShopItem = item
set(value) {
field = value
buyButton.visibility = View.VISIBLE
if (shopItem.unlockCondition == null) {
priceLabel.value = shopItem.value.toDouble()
priceLabel.currency = shopItem.currency
} else {
setBuyButtonEnabled(false)
}
if (shopItem.isLimited) {
//TODO: replace with correct date once API is final
limitedTextView.text = context.getString(R.string.available_until, Date().toString())
} else {
limitedTextView.visibility = View.GONE
}
priceLabel.isLocked = shopItem.locked
val contentView: PurchaseDialogContent
when {
shopItem.isTypeItem -> contentView = PurchaseDialogItemContent(context)
shopItem.isTypeQuest -> {
contentView = PurchaseDialogQuestContent(context)
inventoryRepository.getQuestContent(shopItem.key).first().subscribe(Action1<QuestContent> { contentView.setQuestContent(it) }, RxErrorHandler.handleEmptyError())
}
shopItem.isTypeGear -> {
contentView = PurchaseDialogGearContent(context)
inventoryRepository.getEquipment(shopItem.key).first().subscribe(Action1<Equipment> { contentView.setEquipment(it) }, RxErrorHandler.handleEmptyError())
}
"gems" == shopItem.purchaseType -> contentView = PurchaseDialogGemsContent(context)
else -> contentView = PurchaseDialogBaseContent(context)
}
contentView.setItem(shopItem)
contentContainer.addView(contentView)
setScrollviewSize()
}
private val compositeSubscription: CompositeSubscription = CompositeSubscription()
var shopIdentifier: String? = null
private var user: User? = null
var isPinned: Boolean = false
set(value) {
field = value
if (isPinned) {
pinButton.setImageBitmap(HabiticaIconsHelper.imageOfUnpinItem())
} else {
pinButton.setImageBitmap(HabiticaIconsHelper.imageOfPinItem())
}
}
init {
component.inject(this)
setView(customView)
shopItem = item
compositeSubscription.add(userRepository.user.subscribe(Action1<User> { this.setUser(it) }, RxErrorHandler.handleEmptyError()))
if (!this.configManager.newShopsEnabled()) {
pinButton.visibility = View.GONE
}
closeButton.setOnClickListener { dismiss() }
buyButton.setOnClickListener { onBuyButtonClicked() }
pinButton.setOnClickListener { inventoryRepository.togglePinnedItem(shopItem).subscribe(Action1 { isPinned = !this.isPinned }, RxErrorHandler.handleEmptyError()) }
}
private fun setUser(user: User) {
this.user = user
currencyView.gold = user.stats.getGp()
currencyView.gems = user.gemCount.toDouble()
currencyView.hourglasses = user.hourglassCount.toDouble()
if ("gems" == shopItem.purchaseType) {
val gemsLeft = if (shopItem.limitedNumberLeft != null) shopItem.limitedNumberLeft else 0
val maxGems = user.purchased.plan.totalNumberOfGems()
if (maxGems > 0) {
limitedTextView.text = context.getString(R.string.gems_left_max, gemsLeft, maxGems)
} else {
limitedTextView.text = context.getString(R.string.gems_left_nomax, gemsLeft)
}
limitedTextView.visibility = View.VISIBLE
limitedTextView.setBackgroundColor(ContextCompat.getColor(context, R.color.green_10))
}
if (!shopItem.canAfford(user)) {
priceLabel.cantAfford = true
}
}
override fun dismiss() {
userRepository.close()
inventoryRepository.close()
if (!compositeSubscription.isUnsubscribed) {
compositeSubscription.unsubscribe()
}
super.dismiss()
}
private fun setScrollviewSize() {
scrollView.post {
if (window != null) {
val height = scrollView.getChildAt(0).height
val displayMetrics = DisplayMetrics()
window.windowManager.defaultDisplay.getMetrics(displayMetrics)
val screenHeight = displayMetrics.heightPixels
val spaceRequired = (displayMetrics.density * 160).toInt()
if (height > screenHeight - spaceRequired) {
val myScrollViewParams = scrollView.layoutParams
myScrollViewParams.height = screenHeight - spaceRequired
scrollView.layoutParams = myScrollViewParams
}
}
}
}
private fun onBuyButtonClicked() {
val snackbarText = arrayOf("")
if (shopItem.isValid) {
if (shopItem.locked) {
return
}
if (shopItem.canAfford(user)) {
val observable: Observable<Void>
if (shopIdentifier != null && shopIdentifier == Shop.TIME_TRAVELERS_SHOP || "mystery_set" == shopItem.purchaseType) {
observable = if (shopItem.purchaseType == "gear") {
inventoryRepository.purchaseMysterySet(shopItem.categoryIdentifier)
} else {
inventoryRepository.purchaseHourglassItem(shopItem.purchaseType, shopItem.key)
}
} else if (shopItem.purchaseType == "quests" && shopItem.currency == "gold") {
observable = inventoryRepository.purchaseQuest(shopItem.key)
} else if ("gold" == shopItem.currency && "gem" != shopItem.key) {
observable = inventoryRepository.buyItem(user, shopItem.key, shopItem.value.toDouble()).flatMap { buyResponse ->
if (shopItem.key == "armoire") {
snackbarText[0] = when {
buyResponse.armoire["type"] == "gear" -> context.getString(R.string.armoireEquipment, buyResponse.armoire["dropText"])
buyResponse.armoire["type"] == "food" -> context.getString(R.string.armoireFood, buyResponse.armoire["dropArticle"], buyResponse.armoire["dropText"])
else -> context.getString(R.string.armoireExp)
}
}
Observable.just<Void>(null)
}
} else {
observable = inventoryRepository.purchaseItem(shopItem.purchaseType, shopItem.key)
}
observable
.doOnNext {
val event = ShowSnackbarEvent()
event.title = context.getString(R.string.successful_purchase, shopItem.text)
if (snackbarText[0].isNotEmpty()) {
event.text = snackbarText[0]
}
event.type = HabiticaSnackbar.SnackbarDisplayType.NORMAL
event.rightIcon = priceLabel.compoundDrawables[0]
event.rightTextColor = priceLabel.currentTextColor
event.rightText = "-" + priceLabel.text
EventBus.getDefault().post(event)
}
.flatMap { userRepository.retrieveUser(false, true) }
.flatMap { inventoryRepository.retrieveInAppRewards() }
.subscribe({ }) { throwable ->
if (throwable.javaClass.isAssignableFrom(retrofit2.HttpException::class.java)) {
val error = throwable as retrofit2.HttpException
if (error.code() == 401 && shopItem.currency == "gems") {
EventBus.getDefault().post(OpenGemPurchaseFragmentCommand())
}
}
}
} else {
when {
"gold" == shopItem.currency -> InsufficientGoldDialog(context)
"gems" == shopItem.currency -> InsufficientGemsDialog(context)
"hourglasses" == shopItem.currency -> InsufficientHourglassesDialog(context)
else -> null
}?.show()
}
}
dismiss()
}
private fun setBuyButtonEnabled(enabled: Boolean) {
if (enabled) {
buyButton.alpha = 0.5f
} else {
buyButton.alpha = 1.0f
}
}
}