package com.kickstarter.viewmodels;
import android.support.annotation.NonNull;
import android.util.Pair;
import com.kickstarter.libs.ActivityViewModel;
import com.kickstarter.libs.ApiPaginator;
import com.kickstarter.libs.CurrentConfigType;
import com.kickstarter.libs.CurrentUserType;
import com.kickstarter.libs.Environment;
import com.kickstarter.libs.FeatureKey;
import com.kickstarter.libs.KoalaContext.Update;
import com.kickstarter.libs.rx.transformers.Transformers;
import com.kickstarter.libs.utils.BooleanUtils;
import com.kickstarter.libs.utils.ObjectUtils;
import com.kickstarter.libs.utils.PairUtils;
import com.kickstarter.models.Activity;
import com.kickstarter.models.Project;
import com.kickstarter.models.SurveyResponse;
import com.kickstarter.services.ApiClientType;
import com.kickstarter.services.apiresponses.ActivityEnvelope;
import com.kickstarter.ui.activities.ActivityFeedActivity;
import com.kickstarter.ui.adapters.ActivityFeedAdapter;
import com.kickstarter.ui.viewholders.EmptyActivityFeedViewHolder;
import com.kickstarter.ui.viewholders.FriendBackingViewHolder;
import com.kickstarter.ui.viewholders.ProjectStateChangedPositiveViewHolder;
import com.kickstarter.ui.viewholders.ProjectStateChangedViewHolder;
import com.kickstarter.ui.viewholders.ProjectUpdateViewHolder;
import com.kickstarter.ui.viewholders.UnansweredSurveyViewHolder;
import java.util.ArrayList;
import java.util.List;
import rx.Observable;
import rx.subjects.BehaviorSubject;
import rx.subjects.PublishSubject;
import static com.kickstarter.libs.utils.ObjectUtils.coalesce;
public interface ActivityFeedViewModel {
interface Inputs extends ActivityFeedAdapter.Delegate {
/** Invoke when pagination should happen. */
void nextPage();
/** Invoke when activity's onResume runs */
void resume();
/** Invoke when the feed should be refreshed. */
void refresh();
}
interface Outputs {
/** Emits a list of activities representing the user's activity feed. */
Observable<List<Activity>> activities();
/** Emits when view should be returned to Discovery projects. */
Observable<Void> goToDiscovery();
/** Emits when login tout should be shown. */
Observable<Void> goToLogin();
/** Emits a project when it should be shown. */
Observable<Project> goToProject();
/** Emits an activity when project update should be shown. */
Observable<Activity> goToProjectUpdate();
/** Emits a SurveyResponse when it should be shown. */
Observable<SurveyResponse> goToSurvey();
/** Emits a boolean indicating whether activities are being fetched from the API. */
Observable<Boolean> isFetchingActivities();
/** Emits a boolean that determines if a logged-out, empty state should be displayed. */
Observable<Boolean> loggedOutEmptyStateIsVisible();
/** Emits a logged-in user with zero activities in order to display an empty state. */
Observable<Boolean> loggedInEmptyStateIsVisible();
/** Emits a list of unanswered surveys to be shown in the user's activity feed */
Observable<List<SurveyResponse>> surveys();
}
final class ViewModel extends ActivityViewModel<ActivityFeedActivity> implements
Inputs, Outputs {
private final ApiClientType client;
private final CurrentConfigType currentConfig;
private final CurrentUserType currentUser;
public ViewModel(final @NonNull Environment environment) {
super(environment);
this.client = environment.apiClient();
this.currentConfig = environment.currentConfig();
this.currentUser = environment.currentUser();
this.goToDiscovery = this.discoverProjectsClick;
this.goToLogin = this.loginClick;
this.goToProjectUpdate = this.projectUpdateClick;
this.goToSurvey = this.surveyClick;
this.goToProject = Observable.merge(
this.friendBackingClick,
this.projectStateChangedClick,
this.projectStateChangedPositiveClick,
this.projectUpdateProjectClick
)
.map(Activity::project);
final Observable<Boolean> surveyFeatureEnabled = this.currentConfig.observable()
.map(config -> coalesce(config.features().get(FeatureKey.ANDROID_SURVEYS), false));
Observable.combineLatest(
resume,
this.currentUser.isLoggedIn(),
Pair::create
)
.map(PairUtils::second)
.filter(BooleanUtils::isTrue)
.compose(Transformers.combineLatestPair(surveyFeatureEnabled))
.switchMap(loggedInAndEnabled ->
loggedInAndEnabled.second
? this.client.fetchUnansweredSurveys()
: Observable.just(new ArrayList<SurveyResponse>())
)
.compose(this.bindToLifecycle())
.subscribe(this.surveys::onNext);
final ApiPaginator<Activity, ActivityEnvelope, Void> paginator = ApiPaginator.<Activity, ActivityEnvelope, Void>builder()
.nextPage(this.nextPage)
.startOverWith(this.refresh)
.envelopeToListOfData(ActivityEnvelope::activities)
.envelopeToMoreUrl(env -> env.urls().api().moreActivities())
.loadWithParams(__ -> this.client.fetchActivities())
.loadWithPaginationPath(this.client::fetchActivitiesWithPaginationPath)
.build();
paginator.paginatedData()
.compose(this.bindToLifecycle())
.subscribe(this.activities);
paginator.isFetching()
.compose(this.bindToLifecycle())
.subscribe(this.isFetchingActivities);
this.currentUser.loggedInUser()
.take(1)
.compose(this.bindToLifecycle())
.subscribe(__ -> this.refresh());
this.currentUser.isLoggedIn()
.map(loggedIn -> !loggedIn)
.compose(this.bindToLifecycle())
.subscribe(this.loggedOutEmptyStateIsVisible);
this.currentUser.observable()
.compose(Transformers.takePairWhen(this.activities))
.map(ua -> ua.first != null && ua.second.size() == 0)
.compose(this.bindToLifecycle())
.subscribe(this.loggedInEmptyStateIsVisible);
// Track viewing and paginating activity.
this.nextPage
.compose(Transformers.incrementalCount())
.startWith(0)
.compose(this.bindToLifecycle())
.subscribe(this.koala::trackActivityView);
// Track tapping on any of the activity items.
Observable.merge(
this.friendBackingClick,
this.projectStateChangedPositiveClick,
this.projectStateChangedClick,
this.projectUpdateProjectClick
)
.compose(this.bindToLifecycle())
.subscribe(this.koala::trackActivityTapped);
this.goToProjectUpdate
.map(Activity::project)
.filter(ObjectUtils::isNotNull)
.compose(this.bindToLifecycle())
.subscribe(p -> this.koala.trackViewedUpdate(p, Update.ACTIVITY));
}
private final PublishSubject<Void> discoverProjectsClick = PublishSubject.create();
private final PublishSubject<Activity> friendBackingClick = PublishSubject.create();
private final PublishSubject<Void> loginClick = PublishSubject.create();
private final PublishSubject<Void> nextPage = PublishSubject.create();
private final PublishSubject<Void> resume = PublishSubject.create();
private final PublishSubject<Activity> projectStateChangedClick = PublishSubject.create();
private final PublishSubject<Activity> projectStateChangedPositiveClick = PublishSubject.create();
private final PublishSubject<Activity> projectUpdateClick = PublishSubject.create();
private final PublishSubject<Activity> projectUpdateProjectClick = PublishSubject.create();
private final PublishSubject<Void> refresh = PublishSubject.create();
private final PublishSubject<SurveyResponse> surveyClick = PublishSubject.create();
private final BehaviorSubject<List<Activity>> activities = BehaviorSubject.create();
private final Observable<Void> goToDiscovery;
private final Observable<Void> goToLogin;
private final Observable<Project> goToProject;
private final Observable<Activity> goToProjectUpdate;
private final Observable<SurveyResponse> goToSurvey;
private final BehaviorSubject<Boolean> isFetchingActivities= BehaviorSubject.create();
private final BehaviorSubject<Boolean> loggedInEmptyStateIsVisible = BehaviorSubject.create();
private final BehaviorSubject<Boolean> loggedOutEmptyStateIsVisible = BehaviorSubject.create();
private final BehaviorSubject<List<SurveyResponse>> surveys = BehaviorSubject.create();
public final Inputs inputs = this;
public final Outputs outputs = this;
@Override public void emptyActivityFeedDiscoverProjectsClicked(final @NonNull EmptyActivityFeedViewHolder viewHolder) {
this.discoverProjectsClick.onNext(null);
}
@Override public void emptyActivityFeedLoginClicked(final @NonNull EmptyActivityFeedViewHolder viewHolder) {
this.loginClick.onNext(null);
}
@Override public void friendBackingClicked(final @NonNull FriendBackingViewHolder viewHolder, final @NonNull Activity activity) {
this.friendBackingClick.onNext(activity);
}
@Override public void nextPage() {
this.nextPage.onNext(null);
}
@Override public void projectStateChangedClicked(final @NonNull ProjectStateChangedViewHolder viewHolder,
final @NonNull Activity activity) {
this.projectStateChangedClick.onNext(activity);
}
@Override public void projectStateChangedPositiveClicked(final @NonNull ProjectStateChangedPositiveViewHolder viewHolder,
final @NonNull Activity activity) {
this.projectStateChangedPositiveClick.onNext(activity);
}
@Override public void projectUpdateClicked(final @NonNull ProjectUpdateViewHolder viewHolder,
final @NonNull Activity activity) {
this.projectUpdateClick.onNext(activity);
}
@Override public void projectUpdateProjectClicked(final @NonNull ProjectUpdateViewHolder viewHolder,
final @NonNull Activity activity) {
this.projectUpdateProjectClick.onNext(activity);
}
@Override public void surveyClicked(final @NonNull UnansweredSurveyViewHolder viewHolder, final @NonNull SurveyResponse surveyResponse) {
this.surveyClick.onNext(surveyResponse);
}
@Override public void refresh() {
this.refresh.onNext(null);
}
@Override public void resume() {
this.resume.onNext(null);
}
@Override @NonNull public Observable<List<Activity>> activities() {
return this.activities;
}
@Override @NonNull public Observable<Void> goToDiscovery() {
return this.goToDiscovery;
}
@Override @NonNull public Observable<Void> goToLogin() {
return this.goToLogin;
}
@Override @NonNull public Observable<Project> goToProject() {
return this.goToProject;
}
@Override @NonNull public Observable<Activity> goToProjectUpdate() {
return this.goToProjectUpdate;
}
@Override @NonNull public Observable<SurveyResponse> goToSurvey() {
return this.goToSurvey;
}
@Override @NonNull public Observable<Boolean> isFetchingActivities() {
return this.isFetchingActivities;
}
@Override @NonNull public Observable<Boolean> loggedInEmptyStateIsVisible() {
return this.loggedInEmptyStateIsVisible;
}
@NonNull
@Override public Observable<Boolean> loggedOutEmptyStateIsVisible() {
return this.loggedOutEmptyStateIsVisible;
}
@Override public Observable<List<SurveyResponse>> surveys() {
return this.surveys;
}
}
}