package com.kickstarter.viewmodels; import android.content.SharedPreferences; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Pair; import com.kickstarter.libs.ActivityViewModel; import com.kickstarter.libs.Config; import com.kickstarter.libs.CurrentConfigType; import com.kickstarter.libs.CurrentUserType; import com.kickstarter.libs.Environment; import com.kickstarter.libs.RefTag; import com.kickstarter.libs.utils.RefTagUtils; import com.kickstarter.models.Project; import com.kickstarter.models.User; import com.kickstarter.services.ApiClientType; import com.kickstarter.services.apiresponses.PushNotificationEnvelope; import com.kickstarter.ui.activities.ProjectActivity; import com.kickstarter.ui.adapters.ProjectAdapter; import com.kickstarter.ui.intentmappers.IntentMapper; import com.kickstarter.ui.intentmappers.ProjectIntentMapper; import com.kickstarter.ui.viewholders.ProjectViewHolder; import java.net.CookieManager; import rx.Observable; import rx.subjects.PublishSubject; import static com.kickstarter.libs.rx.transformers.Transformers.combineLatestPair; import static com.kickstarter.libs.rx.transformers.Transformers.ignoreValues; import static com.kickstarter.libs.rx.transformers.Transformers.neverError; import static com.kickstarter.libs.rx.transformers.Transformers.takeWhen; public interface ProjectViewModel { interface Inputs { /** Call when the back project button is clicked. */ void backProjectButtonClicked(); /** Call when the blurb view is clicked. */ void blurbTextViewClicked(); /** Call when the comments text view is clicked. */ void commentsTextViewClicked(); /** Call when the creator name is clicked. */ void creatorNameTextViewClicked(); /** Call when the share button is clicked. */ void shareButtonClicked(); /** Call when the manage pledge button is clicked. */ void managePledgeButtonClicked(); /** Call when the play video button is clicked. */ void playVideoButtonClicked(); /** Call when the star button is clicked. */ void starButtonClicked(); /** Call when the updates button is clicked. */ void updatesTextViewClicked(); /** Call when the view pledge button is clicked. */ void viewPledgeButtonClicked(); } interface Outputs { /** Emits a project and country when a new value is available. If the view model is created with a full project * model, this observable will emit that project immediately, and then again when it has updated from the api. */ Observable<Pair<Project, String>> projectAndUserCountry(); /** Emits when the success prompt for starring should be displayed. */ Observable<Void> showStarredPrompt(); /** Emits when we should start {@link com.kickstarter.ui.activities.LoginToutActivity}. */ Observable<Void> startLoginToutActivity(); /** Emits when we should show the share sheet. */ Observable<Project> showShareSheet(); /** Emits when we should start the campaign {@link com.kickstarter.ui.activities.WebViewActivity}. */ Observable<Project> startCampaignWebViewActivity(); /** Emits when we should start the creator bio {@link com.kickstarter.ui.activities.WebViewActivity}. */ Observable<Project> startCreatorBioWebViewActivity(); /** Emits when we should start {@link com.kickstarter.ui.activities.ProjectUpdatesActivity}. */ Observable<Project> startProjectUpdatesActivity(); /** Emits when we should start {@link com.kickstarter.ui.activities.CommentsActivity}. */ Observable<Project> startCommentsActivity(); /** Emits when we should start the {@link com.kickstarter.ui.activities.CheckoutActivity}. */ Observable<Project> startCheckoutActivity(); /** Emits when we should start the {@link com.kickstarter.ui.activities.CheckoutActivity} to manage the pledge. */ Observable<Project> startManagePledgeActivity(); /** Emits when we should start the {@link com.kickstarter.ui.activities.VideoActivity}. */ Observable<Project> startVideoActivity(); /** Emits when we should start the {@link com.kickstarter.ui.activities.ViewPledgeActivity}. */ Observable<Project> startViewPledgeActivity(); } final class ViewModel extends ActivityViewModel<ProjectActivity> implements ProjectAdapter.Delegate, Inputs, Outputs { private final ApiClientType client; private final CurrentUserType currentUser; private final CookieManager cookieManager; private final CurrentConfigType currentConfig; private final SharedPreferences sharedPreferences; public ViewModel(final @NonNull Environment environment) { super(environment); this.client = environment.apiClient(); this.cookieManager = environment.cookieManager(); this.currentConfig = environment.currentConfig(); this.currentUser = environment.currentUser(); this.sharedPreferences = environment.sharedPreferences(); final Observable<Project> initialProject = intent() .flatMap(i -> ProjectIntentMapper.project(i, this.client)) .share(); // An observable of the ref tag stored in the cookie for the project. Can emit `null`. final Observable<RefTag> cookieRefTag = initialProject .take(1) .map(p -> RefTagUtils.storedCookieRefTagForProject(p, this.cookieManager, this.sharedPreferences)); final Observable<RefTag> refTag = intent() .flatMap(ProjectIntentMapper::refTag); final Observable<PushNotificationEnvelope> pushNotificationEnvelope = intent() .flatMap(ProjectIntentMapper::pushNotificationEnvelope); final Observable<User> loggedInUserOnStarClick = this.currentUser.observable() .compose(takeWhen(this.starButtonClicked)) .filter(u -> u != null); final Observable<User> loggedOutUserOnStarClick = this.currentUser.observable() .compose(takeWhen(this.starButtonClicked)) .filter(u -> u == null); final Observable<Project> projectOnUserChangeStar = initialProject .compose(takeWhen(loggedInUserOnStarClick)) .switchMap(this::toggleProjectStar) .share(); this.startLoginToutActivity = loggedOutUserOnStarClick.compose(ignoreValues()); final Observable<Project> starredProjectOnLoginSuccess = this.startLoginToutActivity .compose(combineLatestPair(this.currentUser.observable())) .filter(su -> su.second != null) .withLatestFrom(initialProject, (__, p) -> p) .take(1) .switchMap(this::starProject) .share(); final Observable<Project> currentProject = Observable.merge( initialProject, projectOnUserChangeStar, starredProjectOnLoginSuccess ); this.showStarredPrompt = projectOnUserChangeStar.mergeWith(starredProjectOnLoginSuccess) .filter(p -> p.isStarred() && p.isLive() && !p.isApproachingDeadline()) .compose(ignoreValues()); this.projectAndUserCountry = currentProject .compose(combineLatestPair(this.currentConfig.observable().map(Config::countryCode))); this.showShareSheet = currentProject.compose(takeWhen(this.shareButtonClicked)); this.startCampaignWebViewActivity = currentProject.compose(takeWhen(this.blurbTextViewClicked)); this.startCheckoutActivity = currentProject.compose(takeWhen(this.backProjectButtonClicked)); this.startCreatorBioWebViewActivity = currentProject.compose(takeWhen(this.creatorNameTextViewClicked)); this.startCommentsActivity = currentProject.compose(takeWhen(this.commentsTextViewClicked)); this.startManagePledgeActivity = currentProject.compose(takeWhen(this.managePledgeButtonClicked)); this.startProjectUpdatesActivity = currentProject.compose(takeWhen(this.updatesTextViewClicked)); this.startVideoActivity = currentProject.compose(takeWhen(this.playVideoButtonClicked)); this.startViewPledgeActivity = currentProject.compose(takeWhen(this.viewPledgeButtonClicked)); this.shareButtonClicked .compose(bindToLifecycle()) .subscribe(__ -> this.koala.trackShowProjectShareSheet()); this.startVideoActivity .compose(bindToLifecycle()) .subscribe(this.koala::trackVideoStart); projectOnUserChangeStar .mergeWith(starredProjectOnLoginSuccess) .compose(bindToLifecycle()) .subscribe(this.koala::trackProjectStar); Observable.combineLatest(refTag, cookieRefTag, currentProject, ProjectViewModel.ViewModel.RefTagsAndProject::new) .take(1) .compose(bindToLifecycle()) .subscribe(data -> { // If a cookie hasn't been set for this ref+project then do so. if (data.refTagFromCookie == null && data.refTagFromIntent != null) { RefTagUtils.storeCookie(data.refTagFromIntent, data.project, this.cookieManager, this.sharedPreferences); } this.koala.trackProjectShow( data.project, data.refTagFromIntent, RefTagUtils.storedCookieRefTagForProject(data.project, this.cookieManager, this.sharedPreferences) ); }); pushNotificationEnvelope .take(1) .compose(bindToLifecycle()) .subscribe(this.koala::trackPushNotification); intent() .filter(IntentMapper::appBannerIsSet) .compose(bindToLifecycle()) .subscribe(__ -> this.koala.trackOpenedAppBanner()); } /** * A light-weight value to hold two ref tags and a project. Two ref tags are stored: one comes from parceled * data in the activity and the other comes from the ref stored in a cookie associated to the project. */ private final class RefTagsAndProject { private final @Nullable RefTag refTagFromIntent; private final @Nullable RefTag refTagFromCookie; private final @NonNull Project project; private RefTagsAndProject(final @Nullable RefTag refTagFromIntent, final @Nullable RefTag refTagFromCookie, final @NonNull Project project) { this.refTagFromIntent = refTagFromIntent; this.refTagFromCookie = refTagFromCookie; this.project = project; } public @NonNull Project project() { return this.project; } } public @NonNull Observable<Project> starProject(final @NonNull Project project) { return this.client.starProject(project) .compose(neverError()); } public @NonNull Observable<Project> toggleProjectStar(final @NonNull Project project) { return this.client.toggleProjectStar(project) .compose(neverError()); } private final PublishSubject<Void> backProjectButtonClicked = PublishSubject.create(); private final PublishSubject<Void> blurbTextViewClicked = PublishSubject.create(); private final PublishSubject<Void> commentsTextViewClicked = PublishSubject.create(); private final PublishSubject<Void> creatorNameTextViewClicked = PublishSubject.create(); private final PublishSubject<Void> managePledgeButtonClicked = PublishSubject.create(); private final PublishSubject<Void> playVideoButtonClicked = PublishSubject.create(); private final PublishSubject<Void> shareButtonClicked = PublishSubject.create(); private final PublishSubject<Void> starButtonClicked = PublishSubject.create(); private final PublishSubject<Void> updatesTextViewClicked = PublishSubject.create(); private final PublishSubject<Void> viewPledgeButtonClicked = PublishSubject.create(); private final Observable<Pair<Project, String>> projectAndUserCountry; private final Observable<Void> startLoginToutActivity; private final Observable<Project> showShareSheet; private final Observable<Void> showStarredPrompt; private final Observable<Project> startCampaignWebViewActivity; private final Observable<Project> startCheckoutActivity; private final Observable<Project> startCommentsActivity; private final Observable<Project> startCreatorBioWebViewActivity; private final Observable<Project> startManagePledgeActivity; private final Observable<Project> startProjectUpdatesActivity; private final Observable<Project> startVideoActivity; private final Observable<Project> startViewPledgeActivity; public final Inputs inputs = this; public final Outputs outputs = this; @Override public void backProjectButtonClicked() { this.backProjectButtonClicked.onNext(null); } @Override public void blurbTextViewClicked() { this.blurbTextViewClicked.onNext(null); } @Override public void commentsTextViewClicked() { this.commentsTextViewClicked.onNext(null); } @Override public void creatorNameTextViewClicked() { this.creatorNameTextViewClicked.onNext(null); } @Override public void managePledgeButtonClicked() { this.managePledgeButtonClicked.onNext(null); } @Override public void playVideoButtonClicked() { this.playVideoButtonClicked.onNext(null); } @Override public void projectViewHolderBackProjectClicked(final @NonNull ProjectViewHolder viewHolder) { this.backProjectButtonClicked(); } @Override public void projectViewHolderBlurbClicked(final @NonNull ProjectViewHolder viewHolder) { this.blurbTextViewClicked(); } @Override public void projectViewHolderCommentsClicked(final @NonNull ProjectViewHolder viewHolder) { this.commentsTextViewClicked(); } @Override public void projectViewHolderCreatorClicked(final @NonNull ProjectViewHolder viewHolder){ this.creatorNameTextViewClicked(); } @Override public void projectViewHolderManagePledgeClicked(final @NonNull ProjectViewHolder viewHolder) { this.managePledgeButtonClicked(); } @Override public void projectViewHolderVideoStarted(final @NonNull ProjectViewHolder viewHolder) { this.playVideoButtonClicked(); } @Override public void projectViewHolderViewPledgeClicked(final @NonNull ProjectViewHolder viewHolder) { this.viewPledgeButtonClicked(); } @Override public void projectViewHolderUpdatesClicked(final @NonNull ProjectViewHolder viewHolder) { this.updatesTextViewClicked(); } @Override public void shareButtonClicked() { this.shareButtonClicked.onNext(null); } @Override public void starButtonClicked() { this.starButtonClicked.onNext(null); } @Override public void updatesTextViewClicked() { this.updatesTextViewClicked.onNext(null); } @Override public void viewPledgeButtonClicked() { this.viewPledgeButtonClicked.onNext(null); } @Override public @NonNull Observable<Project> startVideoActivity() { return this.startVideoActivity; } @Override public @NonNull Observable<Pair<Project, String>> projectAndUserCountry() { return this.projectAndUserCountry; } @Override public @NonNull Observable<Project> startCampaignWebViewActivity() { return this.startCampaignWebViewActivity; } @Override public @NonNull Observable<Project> startCreatorBioWebViewActivity() { return this.startCreatorBioWebViewActivity; } @Override public @NonNull Observable<Project> startCommentsActivity() { return this.startCommentsActivity; } @Override public @NonNull Observable<Void> startLoginToutActivity() { return this.startLoginToutActivity; } @Override public @NonNull Observable<Project> showShareSheet() { return this.showShareSheet; } @Override public @NonNull Observable<Void> showStarredPrompt() { return this.showStarredPrompt; } @Override public @NonNull Observable<Project> startProjectUpdatesActivity() { return this.startProjectUpdatesActivity; } @Override public @NonNull Observable<Project> startCheckoutActivity() { return this.startCheckoutActivity; } @Override public @NonNull Observable<Project> startManagePledgeActivity() { return this.startManagePledgeActivity; } @Override public @NonNull Observable<Project> startViewPledgeActivity() { return this.startViewPledgeActivity; } } }