/*
* Copyright (c) linroid 2015.
*
* 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.linroid.pushapp.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.linroid.pushapp.R;
import timber.log.Timber;
/**
* Created by linroid on 2/3/15.
*/
public class ContentLoaderView extends FrameLayout implements SwipeRefreshLayout.OnRefreshListener, View.OnClickListener {
public static final int LOAD_MORE_ITEM_SLOP = 4;
View loadingView;
View emptyView;
View errorView;
Button emptyRetryBtn;
Button errorRetryBtn;
TextView emptyMessageTV;
TextView errorMessageTV;
public SwipeRefreshLayout refreshLayout;
public RecyclerView recyclerView;
private View contentView;
private OnRefreshListener refreshListener;
private OnMoreListener moreListener;
private boolean loadMore = false;
private int totalPage = 1;
private int currentPage = 1;
private int padding;
private int paddingLeft;
private int paddingRight;
private int paddingTop;
private int paddingBottom;
private boolean clipToPadding;
public static final String DISPLAY_STATE = "display_state";
public static final int STATE_CONTENT = 0x1;
public static final int STATE_LOADING = 0x2;
public static final int STATE_EMPTY = 0x3;
public static final int STATE_ERROR = 0x4;
private int displayState = STATE_LOADING;
public ContentLoaderView(Context context) {
super(context);
initViews();
}
public ContentLoaderView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
initViews();
}
public ContentLoaderView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
initViews();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ContentLoaderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
private void initAttrs(Context ctx, AttributeSet attr) {
TypedArray ta = ctx.obtainStyledAttributes(attr, R.styleable.ContentLoaderView);
padding = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_padding, -1);
paddingLeft = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_paddingLeft, -1);
paddingRight = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_paddingRight, -1);
paddingTop = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_paddingTop, -1);
paddingBottom = ta.getDimensionPixelSize(R.styleable.ContentLoaderView_android_paddingBottom, -1);
clipToPadding = ta.getBoolean(R.styleable.ContentLoaderView_android_clipToPadding, true);
int errorViewId = ta.getResourceId(R.styleable.ContentLoaderView_errorView, R.layout.content_loader_error);
int emptyViewId = ta.getResourceId(R.styleable.ContentLoaderView_emptyView, R.layout.content_loader_empty);
int loadingViewId = ta.getResourceId(R.styleable.ContentLoaderView_loadingView, R.layout.content_loader_loading);
int contentViewId = ta.getResourceId(R.styleable.ContentLoaderView_contentView, R.layout.content_loader_content);
ta.recycle();
LayoutInflater inflater = LayoutInflater.from(ctx);
errorView = inflater.inflate(errorViewId, this, false);
emptyView = inflater.inflate(emptyViewId, this, false);
loadingView = inflater.inflate(loadingViewId, this, false);
contentView = inflater.inflate(contentViewId, this, false);
addView(errorView);
addView(emptyView);
addView(loadingView);
addView(contentView);
}
private void initViews() {
setPadding(0, 0, 0, 0);
setClickable(true);
refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresher);
recyclerView = (RecyclerView) findViewById(R.id.recycler);
errorRetryBtn = (Button) errorView.findViewById(R.id.btn_error_retry);
errorMessageTV = (TextView) errorView.findViewById(R.id.error_message);
emptyRetryBtn = (Button) emptyView.findViewById(R.id.btn_empty_retry);
emptyMessageTV = (TextView) emptyView.findViewById(R.id.empty_message);
if (errorRetryBtn != null) {
errorRetryBtn.setOnClickListener(this);
}
if (emptyRetryBtn != null) {
emptyRetryBtn.setOnClickListener(this);
}
contentView = refreshLayout;
refreshLayout.setEnabled(false);
refreshLayout.setColorSchemeResources(
android.R.color.holo_blue_light,
android.R.color.holo_purple,
android.R.color.holo_green_light,
android.R.color.holo_red_light,
android.R.color.holo_orange_light);
refreshLayout.setOnRefreshListener(this);
recyclerView.addOnScrollListener(mRecyclerScrollListener);
if (padding != -1) {
recyclerView.setPadding(padding, padding, padding, padding);
} else {
recyclerView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
recyclerView.setClipToPadding(clipToPadding);
setDisplayState(STATE_LOADING);
}
RecyclerView.OnScrollListener mRecyclerScrollListener = new RecyclerView.OnScrollListener() {
int totalItemCount;
int visibleItemCount;
int firstVisibleItemPosition;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
RecyclerView.LayoutManager layoutManager;
layoutManager = recyclerView.getLayoutManager();
totalItemCount = layoutManager.getItemCount();
visibleItemCount = layoutManager.getChildCount();
View firstVisibleChild = recyclerView.getChildAt(0);
firstVisibleItemPosition = recyclerView.getChildPosition(firstVisibleChild);
if (totalPage > currentPage &&
!loadMore &&
(firstVisibleItemPosition + visibleItemCount + LOAD_MORE_ITEM_SLOP) >= totalItemCount) {
loadMore = true;
if (moreListener != null) {
moreListener.onMore(++currentPage);
refreshLayout.setEnabled(false);
Timber.d("load more page:%d", currentPage);
}
}
}
};
public void setAdapter(RecyclerView.Adapter adapter) {
recyclerView.setAdapter(adapter);
if (adapter.getItemCount() > 0) {
setDisplayState(STATE_CONTENT);
}
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
super.onChanged();
update();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
update();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
update();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
update();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
update();
}
private void update() {
Timber.d("Adapter updated, itemCount:%d", recyclerView.getLayoutManager().getItemCount());
int itemCount = recyclerView.getLayoutManager().getItemCount();
if (itemCount > 0) {
if (loadMore) {
loadMoreCompleted();
}
setDisplayState(STATE_CONTENT);
} else {
setDisplayState(STATE_EMPTY);
}
if (refreshLayout.isRefreshing()) {
refreshLayout.setRefreshing(false);
}
}
});
}
// @OnClick(R.id.btn_retry)
public void onRetryButtonClick() {
refreshLayout.setRefreshing(true);
setDisplayState(STATE_CONTENT);
if (refreshListener != null) {
refreshListener.onRefresh(false);
}
}
public void notifyLoadFailed(Throwable error) {
if (refreshLayout.isRefreshing()) {
refreshLayout.setRefreshing(false);
}
loadMore = false;
if (currentPage == 1 && recyclerView.getLayoutManager().getChildCount() == 0) {
errorMessageTV.setText(error.getMessage());
setDisplayState(STATE_ERROR);
} else {
setDisplayState(STATE_CONTENT);
Toast.makeText(getContext(), error.getMessage(), Toast.LENGTH_SHORT).show();
}
}
private void loadMoreCompleted() {
loadMore = false;
refreshLayout.setEnabled(true);
setDisplayState(STATE_CONTENT);
}
public void setMoreListener(OnMoreListener moreListener) {
this.moreListener = moreListener;
}
public void setPage(int currentPage, int totalPage) {
this.currentPage = currentPage;
this.totalPage = totalPage;
}
private void setDisplayState(int state) {
this.displayState = state;
loadingView.setVisibility(state == STATE_LOADING ? VISIBLE : GONE);
emptyView.setVisibility(state == STATE_EMPTY ? VISIBLE : GONE);
errorView.setVisibility(state == STATE_ERROR ? VISIBLE : GONE);
contentView.setVisibility(state == STATE_CONTENT ? VISIBLE : GONE);
}
public void setOnRefreshListener(OnRefreshListener refreshListener) {
this.refreshListener = refreshListener;
refreshLayout.setEnabled(true);
}
@Override
public void onRefresh() {
currentPage = 1;
if (refreshListener != null) {
refreshListener.onRefresh(true);
}
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable parcelable = super.onSaveInstanceState();
SavedState savedState = new SavedState(parcelable);
savedState.state = this.displayState;
return savedState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
this.displayState = savedState.state;
setDisplayState(this.displayState);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_error_retry:
case R.id.btn_empty_retry: {
setDisplayState(STATE_LOADING);
onRefresh();
break;
}
}
}
public interface OnRefreshListener {
void onRefresh(boolean fromSwipe);
}
public interface OnMoreListener {
void onMore(int page);
}
static class SavedState extends BaseSavedState {
private int state;
SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
try {
state = source.readInt();
} catch (IllegalArgumentException e) {
state = STATE_LOADING;
}
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(state);
}
public static final Creator<SavedState> CREATOR =
new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}