/* * Copyright 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.plaidapp.data; import android.content.Context; import android.os.AsyncTask; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import io.plaidapp.data.api.designernews.model.StoriesResponse; import io.plaidapp.data.api.dribbble.DribbbleSearch; import io.plaidapp.data.api.dribbble.DribbbleService; import io.plaidapp.data.api.dribbble.model.Like; import io.plaidapp.data.api.dribbble.model.Shot; import io.plaidapp.data.api.dribbble.model.User; import io.plaidapp.data.api.producthunt.model.PostsResponse; import io.plaidapp.data.prefs.SourceManager; import io.plaidapp.ui.FilterAdapter; import retrofit.Callback; import retrofit.RetrofitError; import retrofit.client.Response; /** * Responsible for loading data from the various sources. Instantiating classes are responsible for * providing the {code onDataLoaded} method to do something with the data. */ public abstract class DataManager extends BaseDataManager implements FilterAdapter.FiltersChangedListener, DataLoadingSubject { private final FilterAdapter filterAdapter; private AtomicInteger loadingCount; private Map<String, Integer> pageIndexes; /** * @param filterAdapter */ public DataManager(Context context, FilterAdapter filterAdapter) { super(context); this.filterAdapter = filterAdapter; loadingCount = new AtomicInteger(0); setupPageIndexes(); } public void loadAllDataSources() { for (Source filter : filterAdapter.getFilters()) { loadSource(filter); } } @Override public boolean isDataLoading() { return loadingCount.get() > 0; } @Override public void onFiltersChanged(Source changedFilter){ if (changedFilter.active) { loadSource(changedFilter); } else { // clear the page index for the source pageIndexes.put(changedFilter.key, 0); } } @Override public void onFilterRemoved(Source removed) { } // no-op private void loadSource(Source source) { if (source.active) { loadingCount.incrementAndGet(); int page = getNextPageIndex(source.key); switch (source.key) { case SourceManager.SOURCE_DESIGNER_NEWS_POPULAR: loadDesignerNewsTopStories(page); break; case SourceManager.SOURCE_DESIGNER_NEWS_RECENT: loadDesignerNewsRecent(page); break; case SourceManager.SOURCE_DRIBBBLE_POPULAR: loadDribbblePopular(page); break; case SourceManager.SOURCE_DRIBBBLE_FOLLOWING: loadDribbbleFollowing(page); break; case SourceManager.SOURCE_DRIBBBLE_USER_LIKES: loadDribbbleUserLikes(page); break; case SourceManager.SOURCE_DRIBBBLE_USER_SHOTS: loadDribbbleUserShots(page); break; case SourceManager.SOURCE_DRIBBBLE_RECENT: loadDribbbleRecent(page); break; case SourceManager.SOURCE_DRIBBBLE_DEBUTS: loadDribbbleDebuts(page); break; case SourceManager.SOURCE_DRIBBBLE_ANIMATED: loadDribbbleAnimated(page); break; case SourceManager.SOURCE_PRODUCT_HUNT: loadProductHunt(page); break; default: if (source instanceof Source.DribbbleSearchSource) { loadDribbbleSearch((Source.DribbbleSearchSource) source, page); } else if (source instanceof Source.DesignerNewsSearchSource) { loadDesignerNewsSearch((Source.DesignerNewsSearchSource) source, page); } break; } } } private void setupPageIndexes() { List<Source> dateSources = filterAdapter.getFilters(); pageIndexes = new HashMap<>(dateSources.size()); for (Source source : dateSources) { pageIndexes.put(source.key, 0); } } private int getNextPageIndex(String dataSource) { int nextPage = 1; // default to one – i.e. for newly added sources if (pageIndexes.containsKey(dataSource)) { nextPage = pageIndexes.get(dataSource) + 1; } pageIndexes.put(dataSource, nextPage); return nextPage; } private boolean sourceIsEnabled(String key) { return pageIndexes.get(key) != 0; } private void loadDesignerNewsTopStories(final int page) { getDesignerNewsApi().getTopStories(page, new Callback<StoriesResponse>() { @Override public void success(StoriesResponse storiesResponse, Response response) { if (storiesResponse != null && sourceIsEnabled(SourceManager.SOURCE_DESIGNER_NEWS_POPULAR)) { setPage(storiesResponse.stories, page); setDataSource(storiesResponse.stories, SourceManager.SOURCE_DESIGNER_NEWS_POPULAR); onDataLoaded(storiesResponse.stories); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } private void loadDesignerNewsRecent(final int page) { getDesignerNewsApi().getRecentStories(page, new Callback<StoriesResponse>() { @Override public void success(StoriesResponse storiesResponse, Response response) { if (storiesResponse != null && sourceIsEnabled(SourceManager.SOURCE_DESIGNER_NEWS_RECENT)) { setPage(storiesResponse.stories, page); setDataSource(storiesResponse.stories, SourceManager.SOURCE_DESIGNER_NEWS_RECENT); onDataLoaded(storiesResponse.stories); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } private void loadDesignerNewsSearch(final Source.DesignerNewsSearchSource source, final int page) { getDesignerNewsApi().search(source.query, page, new Callback<StoriesResponse>() { @Override public void success(StoriesResponse storiesResponse, Response response) { if (storiesResponse != null) { setPage(storiesResponse.stories, page); setDataSource(storiesResponse.stories, source.key); onDataLoaded(storiesResponse.stories); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } private void loadDribbblePopular(final int page) { getDribbbleApi().getPopular(page, DribbbleService.PER_PAGE_DEFAULT, new Callback<List<Shot>>() { @Override public void success(List<Shot> shots, Response response) { if (sourceIsEnabled(SourceManager.SOURCE_DRIBBBLE_POPULAR)) { setPage(shots, page); setDataSource(shots, SourceManager.SOURCE_DRIBBBLE_POPULAR); onDataLoaded(shots); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } private void loadDribbbleDebuts(final int page) { getDribbbleApi().getDebuts(page, DribbbleService.PER_PAGE_DEFAULT, new Callback<List<Shot>>() { @Override public void success(List<Shot> shots, Response response) { if (sourceIsEnabled(SourceManager.SOURCE_DRIBBBLE_DEBUTS)) { setPage(shots, page); setDataSource(shots, SourceManager.SOURCE_DRIBBBLE_DEBUTS); onDataLoaded(shots); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } private void loadDribbbleAnimated(final int page) { getDribbbleApi().getAnimated(page, DribbbleService.PER_PAGE_DEFAULT, new Callback<List<Shot>>() { @Override public void success(List<Shot> shots, Response response) { if (sourceIsEnabled(SourceManager.SOURCE_DRIBBBLE_ANIMATED)) { setPage(shots, page); setDataSource(shots, SourceManager.SOURCE_DRIBBBLE_ANIMATED); onDataLoaded(shots); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } private void loadDribbbleRecent(final int page) { getDribbbleApi().getRecent(page, DribbbleService.PER_PAGE_DEFAULT, new Callback<List<Shot>>() { @Override public void success(List<Shot> shots, Response response) { if (sourceIsEnabled(SourceManager.SOURCE_DRIBBBLE_RECENT)) { setPage(shots, page); setDataSource(shots, SourceManager.SOURCE_DRIBBBLE_RECENT); onDataLoaded(shots); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } private void loadDribbbleFollowing(final int page) { if (getDribbblePrefs().isLoggedIn()) { getDribbbleApi().getFollowing(page, DribbbleService.PER_PAGE_DEFAULT, new Callback<List<Shot>>() { @Override public void success(List<Shot> shots, Response response) { if (sourceIsEnabled(SourceManager.SOURCE_DRIBBBLE_FOLLOWING)) { setPage(shots, page); setDataSource(shots, SourceManager.SOURCE_DRIBBBLE_FOLLOWING); onDataLoaded(shots); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } else { loadingCount.decrementAndGet(); } } private void loadDribbbleUserLikes(final int page) { if (getDribbblePrefs().isLoggedIn()) { getDribbbleApi().getUserLikes(page, DribbbleService.PER_PAGE_DEFAULT, new Callback<List<Like>>() { @Override public void success(List<Like> likes, Response response) { if (sourceIsEnabled(SourceManager.SOURCE_DRIBBBLE_USER_LIKES)) { // API returns Likes but we just want the Shots List<Shot> likedShots = new ArrayList<>(likes.size()); for (Like like : likes) { likedShots.add(like.shot); } // these will be sorted like any other shot (popularity per page) // TODO figure out a more appropriate sorting strategy for likes setPage(likedShots, page); setDataSource(likedShots, SourceManager.SOURCE_DRIBBBLE_USER_LIKES); onDataLoaded(likedShots); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } else { loadingCount.decrementAndGet(); } } private void loadDribbbleUserShots(final int page) { if (getDribbblePrefs().isLoggedIn()) { getDribbbleApi().getUserShots(page, DribbbleService.PER_PAGE_DEFAULT, new Callback<List<Shot>>() { @Override public void success(List<Shot> shots, Response response) { if (sourceIsEnabled(SourceManager.SOURCE_DRIBBBLE_USER_SHOTS)) { // this api call doesn't populate the shot user field but we need it User user = getDribbblePrefs().getUser(); for (Shot shot : shots) { shot.user = user; } setPage(shots, page); setDataSource(shots, SourceManager.SOURCE_DRIBBBLE_USER_SHOTS); onDataLoaded(shots); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } else { loadingCount.decrementAndGet(); } } private void loadDribbbleSearch(final Source.DribbbleSearchSource source, final int page) { new AsyncTask<Void, Void, List<Shot>>() { @Override protected List<Shot> doInBackground(Void... params) { return DribbbleSearch.search(source.query, DribbbleSearch.SORT_RECENT, page); } @Override protected void onPostExecute(List<Shot> shots) { if (shots != null && shots.size() > 0 && sourceIsEnabled(source.key)) { setPage(shots, page); setDataSource(shots, source.key); onDataLoaded(shots); } loadingCount.decrementAndGet(); } }.execute(); } private void loadProductHunt(final int page) { // this API's paging is 0 based but this class (& sorting) is 1 based so adjust locally getProductHuntApi().getPosts(page - 1, new Callback<PostsResponse>() { @Override public void success(PostsResponse postsResponse, Response response) { if (postsResponse != null && sourceIsEnabled(SourceManager.SOURCE_PRODUCT_HUNT)) { setPage(postsResponse.posts, page); setDataSource(postsResponse.posts, SourceManager.SOURCE_PRODUCT_HUNT); onDataLoaded(postsResponse.posts); } loadingCount.decrementAndGet(); } @Override public void failure(RetrofitError error) { loadingCount.decrementAndGet(); } }); } }