package com.jakewharton.u2020.ui.trending; import android.annotation.TargetApi; import android.content.Context; import android.graphics.drawable.AnimationDrawable; import android.os.Build; import android.support.v4.content.ContextCompat; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.AttributeSet; import android.view.ContextThemeWrapper; import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; import butterknife.BindView; import butterknife.BindDimen; import butterknife.ButterKnife; import butterknife.OnItemSelected; import com.jakewharton.u2020.R; import com.jakewharton.u2020.data.Funcs; import com.jakewharton.u2020.data.Injector; import com.jakewharton.u2020.data.IntentFactory; import com.jakewharton.u2020.data.api.GithubService; import com.jakewharton.u2020.data.api.Order; import com.jakewharton.u2020.data.api.Results; import com.jakewharton.u2020.data.api.SearchQuery; import com.jakewharton.u2020.data.api.Sort; import com.jakewharton.u2020.data.api.model.RepositoriesResponse; import com.jakewharton.u2020.data.api.model.Repository; import com.jakewharton.u2020.data.api.transforms.SearchResultToRepositoryList; import com.jakewharton.u2020.ui.misc.BetterViewAnimator; import com.jakewharton.u2020.ui.misc.DividerItemDecoration; import com.jakewharton.u2020.ui.misc.EnumAdapter; import com.jakewharton.u2020.util.Intents; import com.squareup.picasso.Picasso; import javax.inject.Inject; import retrofit2.Response; import retrofit2.adapter.rxjava.Result; import rx.Observable; import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; import rx.functions.Func1; import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; import rx.subscriptions.CompositeSubscription; import timber.log.Timber; import static com.jakewharton.u2020.ui.misc.DividerItemDecoration.VERTICAL_LIST; public final class TrendingView extends LinearLayout implements SwipeRefreshLayout.OnRefreshListener, TrendingAdapter.RepositoryClickListener { @BindView(R.id.trending_toolbar) Toolbar toolbarView; @BindView(R.id.trending_timespan) Spinner timespanView; @BindView(R.id.trending_animator) BetterViewAnimator animatorView; @BindView(R.id.trending_swipe_refresh) SwipeRefreshLayout swipeRefreshView; @BindView(R.id.trending_list) RecyclerView trendingView; @BindView(R.id.trending_loading_message) TextView loadingMessageView; @BindDimen(R.dimen.trending_divider_padding_start) float dividerPaddingStart; @Inject GithubService githubService; @Inject Picasso picasso; @Inject IntentFactory intentFactory; @Inject DrawerLayout drawerLayout; private final PublishSubject<TrendingTimespan> timespanSubject; private final EnumAdapter<TrendingTimespan> timespanAdapter; private final TrendingAdapter trendingAdapter; private final CompositeSubscription subscriptions = new CompositeSubscription(); public TrendingView(Context context, AttributeSet attrs) { super(context, attrs); if (!isInEditMode()) { Injector.obtain(context).inject(this); } timespanSubject = PublishSubject.create(); timespanAdapter = new TrendingTimespanAdapter( new ContextThemeWrapper(getContext(), R.style.Theme_U2020_TrendingTimespan)); trendingAdapter = new TrendingAdapter(picasso, this); } @Override protected void onFinishInflate() { super.onFinishInflate(); ButterKnife.bind(this); AnimationDrawable ellipsis = (AnimationDrawable) ContextCompat.getDrawable(getContext(), R.drawable.dancing_ellipsis); loadingMessageView.setCompoundDrawablesWithIntrinsicBounds(null, null, ellipsis, null); ellipsis.start(); toolbarView.setNavigationIcon(R.drawable.menu_icon); toolbarView.setNavigationOnClickListener(v -> drawerLayout.openDrawer(GravityCompat.START)); timespanView.setAdapter(timespanAdapter); timespanView.setSelection(TrendingTimespan.WEEK.ordinal()); swipeRefreshView.setColorSchemeResources(R.color.accent); swipeRefreshView.setOnRefreshListener(this); trendingAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { animatorView.setDisplayedChildId(trendingAdapter.getItemCount() == 0 // ? R.id.trending_empty // : R.id.trending_swipe_refresh); swipeRefreshView.setRefreshing(false); } }); trendingView.setLayoutManager(new LinearLayoutManager(getContext())); trendingView.addItemDecoration( new DividerItemDecoration(getContext(), VERTICAL_LIST, dividerPaddingStart, safeIsRtl())); trendingView.setAdapter(trendingAdapter); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); Observable<Result<RepositoriesResponse>> result = timespanSubject // .flatMap(trendingSearch) // .observeOn(AndroidSchedulers.mainThread()) // .share(); subscriptions.add(result // .filter(Results.isSuccessful()) // .map(SearchResultToRepositoryList.instance()) // .subscribe(trendingAdapter)); subscriptions.add(result // .filter(Funcs.not(Results.isSuccessful())) // .subscribe(trendingError)); // Load the default selection. onRefresh(); } private final Func1<TrendingTimespan, Observable<Result<RepositoriesResponse>>> trendingSearch = new Func1<TrendingTimespan, Observable<Result<RepositoriesResponse>>>() { @Override public Observable<Result<RepositoriesResponse>> call(TrendingTimespan trendingTimespan) { SearchQuery trendingQuery = new SearchQuery.Builder() // .createdSince(trendingTimespan.createdSince()) // .build(); return githubService.repositories(trendingQuery, Sort.STARS, Order.DESC) .subscribeOn(Schedulers.io()); } }; private final Action1<Result<RepositoriesResponse>> trendingError = new Action1<Result<RepositoriesResponse>>() { @Override public void call(Result<RepositoriesResponse> result) { if (result.isError()) { Timber.e(result.error(), "Failed to get trending repositories"); } else { Response<RepositoriesResponse> response = result.response(); Timber.e("Failed to get trending repositories. Server returned %d", response.code()); } swipeRefreshView.setRefreshing(false); animatorView.setDisplayedChildId(R.id.trending_error); } }; @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); subscriptions.unsubscribe(); } @OnItemSelected(R.id.trending_timespan) void timespanSelected(final int position) { if (animatorView.getDisplayedChildId() != R.id.trending_swipe_refresh) { animatorView.setDisplayedChildId(R.id.trending_loading); } // For whatever reason, the SRL's spinner does not draw itself when we call setRefreshing(true) // unless it is posted. post(() -> { swipeRefreshView.setRefreshing(true); timespanSubject.onNext(timespanAdapter.getItem(position)); }); } @Override public void onRefresh() { timespanSelected(timespanView.getSelectedItemPosition()); } @Override public void onRepositoryClick(Repository repository) { Intents.maybeStartActivity(getContext(), intentFactory.createUrlIntent(repository.html_url)); } private boolean safeIsRtl() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && isRtl(); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private boolean isRtl() { return getLayoutDirection() == LAYOUT_DIRECTION_RTL; } }