/*
* Copyright 2016 Hannes Dorfmann.
*
* 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 com.hannesdorfmann.mosby3.sample.mvi.view.home;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.transition.TransitionManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import butterknife.BindInt;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
import com.hannesdorfmann.mosby3.mvi.MviFragment;
import com.hannesdorfmann.mosby3.sample.mvi.R;
import com.hannesdorfmann.mosby3.sample.mvi.SampleApplication;
import com.hannesdorfmann.mosby3.sample.mvi.businesslogic.model.Product;
import com.hannesdorfmann.mosby3.sample.mvi.view.detail.ProductDetailsActivity;
import com.hannesdorfmann.mosby3.sample.mvi.view.ui.GridSpacingItemDecoration;
import com.hannesdorfmann.mosby3.sample.mvi.view.ui.viewholder.ProductViewHolder;
import com.jakewharton.rxbinding2.support.v4.widget.RxSwipeRefreshLayout;
import com.jakewharton.rxbinding2.support.v7.widget.RxRecyclerView;
import io.reactivex.Observable;
import timber.log.Timber;
/**
* @author Hannes Dorfmann
*/
public class HomeFragment extends MviFragment<HomeView, HomePresenter>
implements HomeView, ProductViewHolder.ProductClickedListener {
private HomeAdapter adapter;
private GridLayoutManager layoutManager;
private Unbinder unbinder;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout swipeRefreshLayout;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.loadingView) View loadingView;
@BindView(R.id.errorView) TextView errorView;
@BindInt(R.integer.grid_span_size) int spanCount;
@NonNull @Override public HomePresenter createPresenter() {
Timber.d("createPresenter");
return SampleApplication.getDependencyInjection(getActivity()).newHomePresenter();
}
@Override public void onProductClicked(Product product) {
ProductDetailsActivity.start(getActivity(), product);
}
@Nullable @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home, container, false);
unbinder = ButterKnife.bind(this, view);
adapter = new HomeAdapter(inflater, this);
layoutManager = new GridLayoutManager(getActivity(), spanCount);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override public int getSpanSize(int position) {
int viewType = adapter.getItemViewType(position);
if (viewType == HomeAdapter.VIEW_TYPE_LOADING_MORE_NEXT_PAGE
|| viewType == HomeAdapter.VIEW_TYPE_SECTION_HEADER) {
return spanCount;
}
return 1;
}
});
recyclerView.addItemDecoration(new GridSpacingItemDecoration(spanCount,
getResources().getDimensionPixelSize(R.dimen.grid_spacing), true));
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(layoutManager);
return view;
}
@Override public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
@Override public Observable<Boolean> loadFirstPageIntent() {
return Observable.just(true).doOnComplete(() -> Timber.d("firstPage completed"));
}
@Override public Observable<Boolean> loadNextPageIntent() {
return RxRecyclerView.scrollStateChanges(recyclerView)
.filter(event -> !adapter.isLoadingNextPage())
.filter(event -> event == RecyclerView.SCROLL_STATE_IDLE)
.filter(event -> layoutManager.findLastCompletelyVisibleItemPosition()
== adapter.getItems().size() - 1)
.map(integer -> true);
}
@Override public Observable<Boolean> pullToRefreshIntent() {
return RxSwipeRefreshLayout.refreshes(swipeRefreshLayout).map(ignored -> true);
}
@Override public Observable<String> loadAllProductsFromCategoryIntent() {
return adapter.loadMoreItemsOfCategoryObservable();
}
@Override public void render(HomeViewState viewState) {
Timber.d("render %s", viewState);
if (!viewState.isLoadingFirstPage() && viewState.getFirstPageError() == null) {
renderShowData(viewState);
} else if (viewState.isLoadingFirstPage()) {
renderFirstPageLoading();
} else if (viewState.getFirstPageError() != null) {
renderFirstPageError();
} else {
throw new IllegalStateException("Unknown view state " + viewState);
}
}
private void renderShowData(HomeViewState state) {
TransitionManager.beginDelayedTransition((ViewGroup) getView());
loadingView.setVisibility(View.GONE);
errorView.setVisibility(View.GONE);
swipeRefreshLayout.setVisibility(View.VISIBLE);
boolean changed = adapter.setLoadingNextPage(state.isLoadingNextPage());
if (changed && state.isLoadingNextPage()) {
// scroll to the end of the list so that the user sees the load more progress bar
recyclerView.smoothScrollToPosition(adapter.getItemCount());
}
adapter.setItems(state.getData()); // TODO error: this must be done before setLoading() otherwise error will occure. see https://github.com/sockeqwe/mosby/issues/244
boolean pullToRefreshFinished = swipeRefreshLayout.isRefreshing()
&& !state.isLoadingPullToRefresh()
&& state.getPullToRefreshError() == null;
if (pullToRefreshFinished) {
// Swipe to refresh finished successfully so scroll to the top of the list (to see inserted items)
recyclerView.smoothScrollToPosition(0);
}
swipeRefreshLayout.setRefreshing(state.isLoadingPullToRefresh());
if (state.getNextPageError() != null) {
Snackbar.make(getView(), R.string.error_unknown, Snackbar.LENGTH_LONG)
.show(); // TODO callback
}
if (state.getPullToRefreshError() != null) {
Snackbar.make(getView(), R.string.error_unknown, Snackbar.LENGTH_LONG)
.show(); // TODO callback
}
}
private void renderFirstPageLoading() {
TransitionManager.beginDelayedTransition((ViewGroup) getView());
loadingView.setVisibility(View.VISIBLE);
errorView.setVisibility(View.GONE);
swipeRefreshLayout.setVisibility(View.GONE);
}
private void renderFirstPageError() {
TransitionManager.beginDelayedTransition((ViewGroup) getView());
loadingView.setVisibility(View.GONE);
swipeRefreshLayout.setVisibility(View.GONE);
errorView.setVisibility(View.VISIBLE);
}
}