// Copyright 2015 The Project Buendia Authors
//
// 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 distrib-
// uted 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
// specific language governing permissions and limitations under the License.
package org.projectbuendia.client.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.google.common.base.Charsets;
import org.projectbuendia.client.App;
import org.projectbuendia.client.R;
import java.util.ArrayList;
import java.util.List;
/**
* A {@link Fragment} that shows a spinner or progress bar when fragment content is not ready to
* be displayed.
*/
public abstract class ProgressFragment extends Fragment implements Response.ErrorListener {
protected View mContent;
protected RelativeLayout mFrame;
protected TextView mErrorTextView;
// Fancy progress bar.
protected View mProgressBarLayout;
protected ProgressBar mProgressBar;
protected TextView mProgressBarLabel;
// Indeterminate progress bar.
protected ProgressBar mIndeterminateProgressBar;
protected int mShortAnimationDuration;
private State mState = State.LOADING;
private List<ChangeStateSubscriber> mSubscribers = new ArrayList<ChangeStateSubscriber>();
public enum State {
LOADING,
LOADED,
ERROR
}
/** Subscriber for listening for state changes. */
public interface ChangeStateSubscriber {
/** Called whenever the state is changed. */
public void onChangeState(State newState);
}
public ProgressFragment() {
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mShortAnimationDuration = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
@Override public void onErrorResponse(VolleyError error) {
changeErrorState(error.toString());
Log.e("server", new String(error.networkResponse.data, Charsets.UTF_8));
}
protected void changeErrorState(String message) {
mErrorTextView.setText(message);
changeState(State.ERROR);
}
/** Changes the state of this fragment, hiding or showing the spinner as necessary. */
public void changeState(State state) {
mState = state;
// On state change, always start with the indeterminate loader.
mProgressBarLayout.setVisibility(View.GONE);
mIndeterminateProgressBar.setVisibility(state == State.LOADING ? View.VISIBLE : View.GONE);
mContent.setVisibility(state == State.LOADED ? View.VISIBLE : View.GONE);
mErrorTextView.setVisibility(state == State.ERROR ? View.VISIBLE : View.GONE);
for (ChangeStateSubscriber subscriber : mSubscribers) {
subscriber.onChangeState(mState);
}
}
@Override public void onDestroy() {
super.onDestroy();
App.getServer().cancelPendingRequests();
}
@Override public View onCreateView(
LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mFrame = new RelativeLayout(getActivity());
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT,
RelativeLayout.LayoutParams.MATCH_PARENT);
mFrame.setLayoutParams(layoutParams);
RelativeLayout.LayoutParams relativeLayout = new RelativeLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
relativeLayout.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
mIndeterminateProgressBar = new ProgressBar(getActivity());
mIndeterminateProgressBar.setLayoutParams(relativeLayout);
mProgressBarLayout =
inflater.inflate(R.layout.progress_fragment_measured_progress_view, null);
mProgressBarLayout.setLayoutParams(relativeLayout);
mProgressBar =
(ProgressBar) mProgressBarLayout.findViewById(R.id.progress_fragment_progress_bar);
mProgressBarLabel = (TextView) mProgressBarLayout.findViewById(R.id.progress_fragment_label);
RelativeLayout.LayoutParams fullLayout = new RelativeLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
fullLayout.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
mErrorTextView = new TextView(getActivity());
mErrorTextView.setLayoutParams(fullLayout);
mErrorTextView.setGravity(Gravity.CENTER);
mContent.setVisibility(View.GONE);
mProgressBarLayout.setVisibility(View.GONE);
mFrame.addView(mIndeterminateProgressBar);
mFrame.addView(mProgressBarLayout);
mFrame.addView(mContent);
mFrame.addView(mErrorTextView);
return mFrame;
}
@Override public void onDestroyView() {
super.onDestroyView();
if (mFrame != null) {
mFrame.removeAllViewsInLayout();
}
}
/** Registers a {@link ChangeStateSubscriber}. */
public void registerSubscriber(ChangeStateSubscriber subscriber) {
mSubscribers.add(subscriber);
}
/** Unregisters a {@link ChangeStateSubscriber} if the subscriber is currently registered. */
public void unregisterSubscriber(ChangeStateSubscriber subscriber) {
if (mSubscribers.contains(subscriber)) {
mSubscribers.remove(subscriber);
}
}
public State getState() {
return mState;
}
protected void setContentView(int layout) {
mContent = LayoutInflater.from(getActivity()).inflate(layout, null, false);
}
protected void incrementProgressBy(int progress) {
switchToHorizontalProgressBar();
mProgressBar.incrementProgressBy(progress);
}
protected void switchToHorizontalProgressBar() {
if (mState == State.LOADING) {
mIndeterminateProgressBar.setVisibility(View.GONE);
mProgressBarLayout.setVisibility(View.VISIBLE);
}
}
protected void setProgress(int progress) {
switchToHorizontalProgressBar();
mProgressBar.setProgress(progress);
}
protected void setProgressLabel(String label) {
switchToHorizontalProgressBar();
mProgressBarLabel.setText(label);
}
protected void switchToCircularProgressBar() {
if (mState == State.LOADING) {
mIndeterminateProgressBar.setVisibility(View.VISIBLE);
mProgressBarLayout.setVisibility(View.GONE);
}
}
private void crossfade(View inView, final View outView) {
// Set the content view to 0% opacity but visible, so that it is visible
// (but fully transparent) during the animation.
inView.setAlpha(0f);
inView.setVisibility(View.VISIBLE);
// Animate the content view to 100% opacity, and clear any animation
// listener set on the view.
inView.animate()
.alpha(1f)
.setDuration(mShortAnimationDuration)
.setListener(null);
// Animate the loading view to 0% opacity. After the animation ends,
// set its visibility to GONE as an optimization step (it won't
// participate in layout passes, etc.)
if (outView != null) {
outView.animate()
.alpha(0f)
.setDuration(mShortAnimationDuration)
.setListener(new AnimatorListenerAdapter() {
@Override public void onAnimationEnd(Animator animation) {
outView.setVisibility(View.GONE);
}
});
}
}
}