package com.kickstarter.viewmodels; import android.support.annotation.NonNull; import android.util.Pair; import com.kickstarter.libs.ActivityViewModel; import com.kickstarter.libs.Config; import com.kickstarter.libs.CurrentConfigType; import com.kickstarter.libs.Environment; import com.kickstarter.libs.KSCurrency; import com.kickstarter.libs.utils.BackingUtils; import com.kickstarter.libs.utils.BooleanUtils; import com.kickstarter.libs.utils.IntegerUtils; import com.kickstarter.libs.utils.NumberUtils; import com.kickstarter.libs.utils.ObjectUtils; import com.kickstarter.libs.utils.ProjectUtils; import com.kickstarter.libs.utils.RewardUtils; import com.kickstarter.models.Project; import com.kickstarter.models.Reward; import com.kickstarter.models.RewardsItem; import com.kickstarter.ui.viewholders.RewardViewHolder; import com.kickstarter.viewmodels.inputs.RewardViewModelInputs; import com.kickstarter.viewmodels.outputs.RewardViewModelOutputs; import org.joda.time.DateTime; import java.math.RoundingMode; import java.util.ArrayList; import java.util.List; import rx.Observable; import rx.subjects.BehaviorSubject; import rx.subjects.PublishSubject; import static com.kickstarter.libs.rx.transformers.Transformers.coalesce; import static com.kickstarter.libs.rx.transformers.Transformers.combineLatestPair; import static com.kickstarter.libs.rx.transformers.Transformers.takeWhen; public final class RewardViewModel extends ActivityViewModel<RewardViewHolder> implements RewardViewModelInputs, RewardViewModelOutputs { public RewardViewModel(final @NonNull Environment environment) { super(environment); final CurrentConfigType currentConfig = environment.currentConfig(); final KSCurrency ksCurrency = environment.ksCurrency(); final Observable<String> formattedMinimum = projectAndReward .map(pr -> ksCurrency.format(pr.second.minimum(), pr.first)); final Observable<Boolean> isSelectable = projectAndReward .map(pr -> isSelectable(pr.first, pr.second)); final Observable<Project> project = projectAndReward .map(pr -> pr.first); final Observable<Reward> reward = projectAndReward .map(pr -> pr.second); final Observable<Boolean> rewardIsSelected = projectAndReward .map(pr -> BackingUtils.isBacked(pr.first, pr.second)); final Observable<Boolean> shouldDisplayUsdConversion = currentConfig.observable() .map(Config::countryCode) .compose(combineLatestPair(project.map(Project::country))) .map(configCountryAndProjectCountry -> ProjectUtils.isUSUserViewingNonUSProject(configCountryAndProjectCountry.first, configCountryAndProjectCountry.second)); // Hide 'all gone' header if limit has not been reached, or reward has been backed by user. projectAndReward .map(pr -> !RewardUtils.isLimitReached(pr.second) || BackingUtils.isBacked(pr.first, pr.second)) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(allGoneTextViewIsHidden); reward .map(r -> RewardUtils.isNoReward(r) || !RewardUtils.hasBackers(r)) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(backersTextViewIsHidden); reward .filter(r -> RewardUtils.isReward(r) || RewardUtils.hasBackers(r)) .map(Reward::backersCount) .filter(ObjectUtils::isNotNull) .compose(bindToLifecycle()) .subscribe(backersTextViewText); reward .map(Reward::description) .compose(bindToLifecycle()) .subscribe(descriptionTextViewText); reward .map(Reward::estimatedDeliveryOn) .filter(ObjectUtils::isNotNull) .compose(bindToLifecycle()) .subscribe(estimatedDeliveryDateTextViewText); reward .map(Reward::estimatedDeliveryOn) .map(ObjectUtils::isNull) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(estimatedDeliveryDateSectionIsHidden); isSelectable .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(isClickable); projectAndReward .filter(pr -> isSelectable(pr.first, pr.second) && pr.first.isLive()) .compose(takeWhen(rewardClicked)) .compose(bindToLifecycle()) .subscribe(goToCheckout); projectAndReward .compose(takeWhen(rewardClicked)) .filter(pr -> ProjectUtils.isCompleted(pr.first) && BackingUtils.isBacked(pr.first, pr.second)) .map(pr -> pr.first) .compose(bindToLifecycle()) .subscribe(goToViewPledge); reward .map(r -> IntegerUtils.isNonZero(r.limit()) && IntegerUtils.isNonZero(r.backersCount())) .map(BooleanUtils::negate) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(limitAndBackersSeparatorIsHidden); reward .map(RewardUtils::isLimited) .map(BooleanUtils::negate) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(limitAndRemainingTextViewIsHidden); reward .map(r -> Pair.create(r.limit(), r.remaining())) .filter(lr -> lr.first != null && lr.second != null) .map(rr -> Pair.create(NumberUtils.format(rr.first), NumberUtils.format(rr.second))) .compose(bindToLifecycle()) .subscribe(limitAndRemainingTextViewText); // Hide limit header if reward is not limited, or reward has been backed by user. projectAndReward .map(pr -> !RewardUtils.isLimited(pr.second) || BackingUtils.isBacked(pr.first, pr.second)) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(limitHeaderIsHidden); formattedMinimum .compose(bindToLifecycle()) .subscribe(minimumTextViewText); reward .map(Reward::rewardsItems) .compose(coalesce(new ArrayList<RewardsItem>())) .compose(bindToLifecycle()) .subscribe(rewardsItems); reward .map(RewardUtils::isItemized) .map(BooleanUtils::negate) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(rewardsItemsAreHidden); rewardIsSelected .map(BooleanUtils::negate) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(selectedHeaderIsHidden); rewardIsSelected .map(BooleanUtils::negate) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(selectedOverlayIsHidden); reward .filter(RewardUtils::isShippable) .map(Reward::shippingSummary) .compose(bindToLifecycle()) .subscribe(shippingSummaryTextViewText); reward .map(RewardUtils::isShippable) .map(BooleanUtils::negate) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(shippingSummarySectionIsHidden); reward .map(Reward::title) .map(ObjectUtils::isNull) .compose(bindToLifecycle()) .subscribe(titleTextViewIsHidden); reward .map(Reward::description) .map(String::isEmpty) .compose(bindToLifecycle()) .subscribe(rewardDescriptionIsHidden); reward .map(Reward::title) .filter(ObjectUtils::isNotNull) .compose(bindToLifecycle()) .subscribe(titleTextViewText); shouldDisplayUsdConversion .map(BooleanUtils::negate) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(usdConversionTextViewIsHidden); projectAndReward .map(pr -> ksCurrency.format(pr.second.minimum(), pr.first, true, true, RoundingMode.UP)) .compose(takeWhen( shouldDisplayUsdConversion .filter(BooleanUtils::isTrue) )) .compose(bindToLifecycle()) .subscribe(usdConversionTextViewText); projectAndReward .map(pr -> RewardUtils.isLimitReached(pr.second) && !BackingUtils.isBacked(pr.first, pr.second)) .map(BooleanUtils::negate) .distinctUntilChanged() .compose(bindToLifecycle()) .subscribe(whiteOverlayIsHidden); } private static boolean isSelectable(final @NonNull Project project, final @NonNull Reward reward) { if (BackingUtils.isBacked(project, reward)) { return true; } if (!project.isLive()) { return false; } return !RewardUtils.isLimitReached(reward); } private final PublishSubject<Pair<Project, Reward>> projectAndReward = PublishSubject.create(); private final PublishSubject<Void> rewardClicked = PublishSubject.create(); private final BehaviorSubject<Boolean> allGoneTextViewIsHidden = BehaviorSubject.create(); private final BehaviorSubject<Boolean> backersTextViewIsHidden = BehaviorSubject.create(); private final BehaviorSubject<Integer> backersTextViewText = BehaviorSubject.create(); private final BehaviorSubject<String> descriptionTextViewText = BehaviorSubject.create(); private final BehaviorSubject<DateTime> estimatedDeliveryDateTextViewText = BehaviorSubject.create(); private final BehaviorSubject<Boolean> estimatedDeliveryDateSectionIsHidden = BehaviorSubject.create(); private final PublishSubject<Pair<Project, Reward>> goToCheckout = PublishSubject.create(); private final PublishSubject<Project> goToViewPledge = PublishSubject.create(); private final BehaviorSubject<Boolean> isClickable = BehaviorSubject.create(); private final BehaviorSubject<Boolean> limitAndBackersSeparatorIsHidden = BehaviorSubject.create(); private final BehaviorSubject<Boolean> limitAndRemainingTextViewIsHidden = BehaviorSubject.create(); private final BehaviorSubject<Pair<String, String>> limitAndRemainingTextViewText = BehaviorSubject.create(); private final BehaviorSubject<Boolean> limitHeaderIsHidden = BehaviorSubject.create(); private final BehaviorSubject<String> minimumTextViewText = BehaviorSubject.create(); private final BehaviorSubject<List<RewardsItem>> rewardsItems = BehaviorSubject.create(); private final BehaviorSubject<Boolean> rewardsItemsAreHidden = BehaviorSubject.create(); private final BehaviorSubject<Boolean> titleTextViewIsHidden = BehaviorSubject.create(); private final BehaviorSubject<String> titleTextViewText = BehaviorSubject.create(); private final BehaviorSubject<Boolean> selectedHeaderIsHidden = BehaviorSubject.create(); private final BehaviorSubject<Boolean> selectedOverlayIsHidden = BehaviorSubject.create(); private final BehaviorSubject<Boolean> shippingSummarySectionIsHidden = BehaviorSubject.create(); private final BehaviorSubject<String> shippingSummaryTextViewText = BehaviorSubject.create(); private final BehaviorSubject<String> usdConversionTextViewText = BehaviorSubject.create(); private final BehaviorSubject<Boolean> usdConversionTextViewIsHidden = BehaviorSubject.create(); private final BehaviorSubject<Boolean> whiteOverlayIsHidden = BehaviorSubject.create(); private final BehaviorSubject<Boolean> rewardDescriptionIsHidden = BehaviorSubject.create(); public final RewardViewModelInputs inputs = this; public final RewardViewModelOutputs outputs = this; @Override public void projectAndReward(final @NonNull Project project, final @NonNull Reward reward) { this.projectAndReward.onNext(Pair.create(project, reward)); } @Override public void rewardClicked() { rewardClicked.onNext(null); } @Override public @NonNull Observable<Boolean> allGoneTextViewIsHidden() { return allGoneTextViewIsHidden; } @Override public @NonNull Observable<Boolean> backersTextViewIsHidden() { return backersTextViewIsHidden; } @Override public @NonNull Observable<Integer> backersTextViewText() { return backersTextViewText; } @Override public @NonNull Observable<Boolean> isClickable() { return isClickable; } @Override public @NonNull Observable<String> descriptionTextViewText() { return descriptionTextViewText; } @Override public @NonNull Observable<DateTime> estimatedDeliveryDateTextViewText() { return estimatedDeliveryDateTextViewText; } @Override public @NonNull Observable<Boolean> estimatedDeliveryDateSectionIsHidden() { return estimatedDeliveryDateSectionIsHidden; } @Override public @NonNull Observable<Pair<Project, Reward>> goToCheckout() { return goToCheckout; } @Override public @NonNull Observable<Project> goToViewPledge() { return goToViewPledge; } @Override public @NonNull Observable<Boolean> limitAndBackersSeparatorIsHidden() { return limitAndBackersSeparatorIsHidden; } @Override public @NonNull Observable<Boolean> limitAndRemainingTextViewIsHidden() { return limitAndRemainingTextViewIsHidden; } @Override public @NonNull Observable<Pair<String, String>> limitAndRemainingTextViewText() { return limitAndRemainingTextViewText; } @Override public @NonNull Observable<Boolean> limitHeaderIsHidden() { return limitHeaderIsHidden; } @Override public @NonNull Observable<String> minimumTextViewText() { return minimumTextViewText; } @Override public @NonNull Observable<List<RewardsItem>> rewardsItems() { return rewardsItems; } @Override public @NonNull Observable<Boolean> rewardsItemsAreHidden() { return rewardsItemsAreHidden; } @Override public @NonNull Observable<Boolean> selectedHeaderIsHidden() { return selectedHeaderIsHidden; } @Override public @NonNull Observable<Boolean> selectedOverlayIsHidden() { return selectedOverlayIsHidden; } @Override public @NonNull Observable<Boolean> shippingSummarySectionIsHidden() { return shippingSummarySectionIsHidden; } @Override public @NonNull Observable<String> shippingSummaryTextViewText() { return shippingSummaryTextViewText; } @Override public @NonNull Observable<Boolean> titleTextViewIsHidden() { return titleTextViewIsHidden; } @Override public @NonNull Observable<String> titleTextViewText() { return titleTextViewText; } @Override public @NonNull Observable<Boolean> usdConversionTextViewIsHidden() { return usdConversionTextViewIsHidden; } @Override public @NonNull Observable<String> usdConversionTextViewText() { return usdConversionTextViewText; } @Override public @NonNull Observable<Boolean> whiteOverlayIsHidden() { return whiteOverlayIsHidden; } @Override public @NonNull Observable<Boolean> rewardDescriptionIsHidden() { return rewardDescriptionIsHidden; } }