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.Environment;
import com.kickstarter.libs.RefTag;
import com.kickstarter.libs.utils.ListUtils;
import com.kickstarter.libs.utils.StringUtils;
import com.kickstarter.models.Project;
import com.kickstarter.services.ApiClientType;
import com.kickstarter.services.DiscoveryParams;
import com.kickstarter.services.apiresponses.DiscoverEnvelope;
import com.kickstarter.ui.activities.SearchActivity;
import java.util.List;
import java.util.concurrent.TimeUnit;
import rx.Observable;
import rx.Scheduler;
import rx.subjects.BehaviorSubject;
import rx.subjects.PublishSubject;
import static com.kickstarter.libs.rx.transformers.Transformers.takePairWhen;
public interface SearchViewModel {
interface Inputs {
/** Call when the next page has been invoked. */
void nextPage();
/** Call when a project is tapped in search results. */
void projectClicked(final @NonNull Project project);
/** Call when text changes in search box. */
void search(final @NonNull String s);
}
interface Outputs {
/** Emits list of popular projects. */
Observable<List<Project>> popularProjects();
/** Emits list of projects matching criteria. */
Observable<List<Project>> searchProjects();
/** Emits a project and ref tag when we should start a project activity. */
Observable<Pair<Project, RefTag>> startProjectActivity();
}
final class ViewModel extends ActivityViewModel<SearchActivity> implements Inputs, Outputs {
public ViewModel(final @NonNull Environment environment) {
super(environment);
final ApiClientType apiClient = environment.apiClient();
final Scheduler scheduler = environment.scheduler();
final Observable<DiscoveryParams> searchParams = this.search
.filter(StringUtils::isPresent)
.debounce(300, TimeUnit.MILLISECONDS, scheduler)
.map(s -> DiscoveryParams.builder().term(s).build());
final Observable<DiscoveryParams> popularParams = this.search
.filter(StringUtils::isEmpty)
.map(__ -> defaultParams)
.startWith(defaultParams);
final Observable<DiscoveryParams> params = Observable.merge(searchParams, popularParams);
final ApiPaginator<Project, DiscoverEnvelope, DiscoveryParams> paginator =
ApiPaginator.<Project, DiscoverEnvelope, DiscoveryParams>builder()
.nextPage(this.nextPage)
.startOverWith(params)
.envelopeToListOfData(DiscoverEnvelope::projects)
.envelopeToMoreUrl(env -> env.urls().api().moreProjects())
.clearWhenStartingOver(true)
.concater(ListUtils::concatDistinct)
.loadWithParams(apiClient::fetchProjects)
.loadWithPaginationPath(apiClient::fetchProjects)
.build();
this.search
.filter(StringUtils::isEmpty)
.compose(bindToLifecycle())
.subscribe(__ -> {
this.searchProjects.onNext(ListUtils.empty());
this.koala.trackClearedSearchTerm();
});
params
.compose(takePairWhen(paginator.paginatedData()))
.compose(bindToLifecycle())
.subscribe(paramsAndProjects -> {
if (paramsAndProjects.first.sort() == defaultSort) {
this.popularProjects.onNext(paramsAndProjects.second);
} else {
this.searchProjects.onNext(paramsAndProjects.second);
}
});
final Observable<Integer> pageCount = paginator.loadingPage();
final Observable<String> query = params
.map(DiscoveryParams::term);
final Observable<List<Project>> projects = Observable.merge(this.popularProjects, this.searchProjects);
this.startProjectActivity = Observable.combineLatest(this.search, projects, Pair::create)
.compose(takePairWhen(this.projectClicked))
.map(searchTermAndProjectsAndProjectClicked -> {
final String searchTerm = searchTermAndProjectsAndProjectClicked.first.first;
final List<Project> currentProjects = searchTermAndProjectsAndProjectClicked.first.second;
final Project projectClicked = searchTermAndProjectsAndProjectClicked.second;
return this.projectAndRefTag(searchTerm, currentProjects, projectClicked);
});
query
.compose(takePairWhen(pageCount))
.filter(qp -> StringUtils.isPresent(qp.first))
.compose(bindToLifecycle())
.subscribe(qp -> this.koala.trackSearchResults(qp.first, qp.second));
this.koala.trackSearchView();
}
private static final DiscoveryParams.Sort defaultSort = DiscoveryParams.Sort.POPULAR;
private static final DiscoveryParams defaultParams = DiscoveryParams.builder().sort(defaultSort).build();
/**
* Returns a project and its appropriate ref tag given its location in a list of popular projects or search results.
*
* @param searchTerm The search term entered to determine list of search results.
* @param projects The list of popular or search result projects.
* @param selectedProject The project selected by the user.
* @return The project and its appropriate ref tag.
*/
private @NonNull Pair<Project, RefTag> projectAndRefTag(final @NonNull String searchTerm,
final @NonNull List<Project> projects, final @NonNull Project selectedProject) {
final boolean isFirstResult = selectedProject == projects.get(0);
if (searchTerm.length() == 0) {
return isFirstResult
? Pair.create(selectedProject, RefTag.searchPopularFeatured())
: Pair.create(selectedProject, RefTag.searchPopular());
} else {
return isFirstResult
? Pair.create(selectedProject, RefTag.searchFeatured())
: Pair.create(selectedProject, RefTag.search());
}
}
private final PublishSubject<Void> nextPage = PublishSubject.create();
private final PublishSubject<Project> projectClicked = PublishSubject.create();
private final PublishSubject<String> search = PublishSubject.create();
private final BehaviorSubject<List<Project>> popularProjects = BehaviorSubject.create();
private final BehaviorSubject<List<Project>> searchProjects = BehaviorSubject.create();
private final Observable<Pair<Project, RefTag>> startProjectActivity;
public final SearchViewModel.Inputs inputs = this;
public final SearchViewModel.Outputs outputs = this;
@Override public void nextPage() {
this.nextPage.onNext(null);
}
@Override public void projectClicked(final @NonNull Project project) {
this.projectClicked.onNext(project);
}
@Override public void search(final @NonNull String s) {
this.search.onNext(s);
}
@Override public @NonNull Observable<Pair<Project, RefTag>> startProjectActivity() {
return this.startProjectActivity;
}
@Override public @NonNull Observable<List<Project>> popularProjects() {
return this.popularProjects;
}
@Override public @NonNull Observable<List<Project>> searchProjects() {
return this.searchProjects;
}
}
}