content: /*
  * Copyright 2013 The Android Open Source Project
  *
  * 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.example.android.batchstepsensor.cardstream;
  
  import android.animation.Animator;
  import android.animation.AnimatorListenerAdapter;
  import android.animation.ObjectAnimator;
  import android.app.Activity;
  import android.graphics.Color;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.Button;
  import android.widget.ProgressBar;
  import android.widget.TextView;
  
  import com.example.android.batchstepsensor.R;
  
  import java.util.ArrayList;
  
  /**
   * A Card contains a description and has a visual state. Optionally a card also contains a title,
   * progress indicator and zero or more actions. It is constructed through the {@link Builder}.
   */
  public class Card {
  
      public static final int ACTION_POSITIVE = 1;
      public static final int ACTION_NEGATIVE = 2;
      public static final int ACTION_NEUTRAL = 3;
  
      public static final int PROGRESS_TYPE_NO_PROGRESS = 0;
      public static final int PROGRESS_TYPE_NORMAL = 1;
      public static final int PROGRESS_TYPE_INDETERMINATE = 2;
      public static final int PROGRESS_TYPE_LABEL = 3;
  
      private OnCardClickListener mClickListener;
  
  
      // The card model contains a reference to its desired layout (for extensibility), title,
      // description, zero to many action buttons, and zero or 1 progress indicators.
      private int mLayoutId = R.layout.card;
  
      /**
       * Tag that uniquely identifies this card.
       */
      private String mTag = null;
  
      private String mTitle = null;
      private String mDescription = null;
  
      private View mCardView = null;
      private View mOverlayView = null;
      private TextView mTitleView = null;
      private TextView mDescView = null;
      private View mActionAreaView = null;
  
      private Animator mOngoingAnimator = null;
  
      /**
       * Visual state, either {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED} or
       * {@link #CARD_STATE_INACTIVE}.
       */
      private int mCardState = CARD_STATE_NORMAL;
      public static final int CARD_STATE_NORMAL = 1;
      public static final int CARD_STATE_FOCUSED = 2;
      public static final int CARD_STATE_INACTIVE = 3;
  
      /**
       * Represent actions that can be taken from the card.  Stylistically the developer can
       * designate the action as positive, negative (ok/cancel, for instance), or neutral.
       * This "type" can be used as a UI hint.
       * @see com.example.android.sensors.batchstepsensor.Card.CardAction
       */
      private ArrayList<CardAction> mCardActions = new ArrayList<CardAction>();
  
      /**
       * Some cards will have a sense of "progress" which should be associated with, but separated
       * from its "parent" card.  To push for simplicity in samples, Cards are designed to have
       * a maximum of one progress indicator per Card.
       */
      private CardProgress mCardProgress = null;
  
      public Card() {
      }
  
      public String getTag() {
          return mTag;
      }
  
      public View getView() {
          return mCardView;
      }
  
  
      public Card setDescription(String desc) {
          if (mDescView != null) {
              mDescription = desc;
              mDescView.setText(desc);
          }
          return this;
      }
  
      public Card setTitle(String title) {
          if (mTitleView != null) {
              mTitle = title;
              mTitleView.setText(title);
          }
          return this;
      }
  
  
      /**
       * Return the UI state, either {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED}
       * or {@link #CARD_STATE_INACTIVE}.
       */
      public int getState() {
          return mCardState;
      }
  
      /**
       * Set the UI state. The parameter describes the state and must be either
       * {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED} or {@link #CARD_STATE_INACTIVE}.
       * Note: This method must be called from the UI Thread.
       * @param state
       * @return The card itself, allows for chaining of calls
       */
      public Card setState(int state) {
          mCardState = state;
          if (null != mOverlayView) {
              if (null != mOngoingAnimator) {
                  mOngoingAnimator.end();
                  mOngoingAnimator = null;
              }
              switch (state) {
                  case CARD_STATE_NORMAL: {
                      mOverlayView.setVisibility(View.GONE);
                      mOverlayView.setAlpha(1.f);
                      break;
                  }
                  case CARD_STATE_FOCUSED: {
                      mOverlayView.setVisibility(View.VISIBLE);
                      mOverlayView.setBackgroundResource(R.drawable.card_overlay_focused);
                      ObjectAnimator animator = ObjectAnimator.ofFloat(mOverlayView, "alpha", 0.f);
                      animator.setRepeatMode(ObjectAnimator.REVERSE);
                      animator.setRepeatCount(ObjectAnimator.INFINITE);
                      animator.setDuration(1000);
                      animator.start();
                      mOngoingAnimator = animator;
                      break;
                  }
                  case CARD_STATE_INACTIVE: {
                      mOverlayView.setVisibility(View.VISIBLE);
                      mOverlayView.setAlpha(1.f);
                      mOverlayView.setBackgroundColor(Color.argb(0xaa, 0xcc, 0xcc, 0xcc));
                      break;
                  }
              }
          }
          return this;
      }
  
      /**
       * Set the type of progress indicator.
       * The progress type can only be changed if the Card was initially build with a progress
       * indicator.
       * See {@link Builder#setProgressType(int)}.
       * Must be a value of either {@link #PROGRESS_TYPE_NORMAL},
       * {@link #PROGRESS_TYPE_INDETERMINATE}, {@link #PROGRESS_TYPE_LABEL} or
       * {@link #PROGRESS_TYPE_NO_PROGRESS}.
       * @param progressType
       * @return The card itself, allows for chaining of calls
       */
      public Card setProgressType(int progressType) {
          if (mCardProgress == null) {
              mCardProgress = new CardProgress();
          }
          mCardProgress.setProgressType(progressType);
          return this;
      }
  
      /**
       * Return the progress indicator type. A value of either {@link #PROGRESS_TYPE_NORMAL},
       * {@link #PROGRESS_TYPE_INDETERMINATE}, {@link #PROGRESS_TYPE_LABEL}. Otherwise if no progress
       * indicator is enabled, {@link #PROGRESS_TYPE_NO_PROGRESS} is returned.
       * @return
       */
      public int getProgressType() {
          if (mCardProgress == null) {
              return PROGRESS_TYPE_NO_PROGRESS;
          }
          return mCardProgress.progressType;
      }
  
      /**
       * Set the progress to the specified value. Only applicable if the card has a
       * {@link #PROGRESS_TYPE_NORMAL} progress type.
       * @param progress
       * @return
       * @see #setMaxProgress(int)
       */
      public Card setProgress(int progress) {
          if (mCardProgress != null) {
              mCardProgress.setProgress(progress);
          }
          return this;
      }
  
      /**
       * Set the range of the progress to 0...max. Only applicable if the card has a
       * {@link #PROGRESS_TYPE_NORMAL} progress type.
       * @return
       */
      public Card setMaxProgress(int max){
          if (mCardProgress != null) {
              mCardProgress.setMax(max);
          }
          return this;
      }
  
      /**
       * Set the label text for the progress if the card has a progress type of
       * {@link #PROGRESS_TYPE_NORMAL}, {@link #PROGRESS_TYPE_INDETERMINATE} or
       * {@link #PROGRESS_TYPE_LABEL}
       * @param text
       * @return
       */
      public Card setProgressLabel(String text) {
          if (mCardProgress != null) {
              mCardProgress.setProgressLabel(text);
          }
          return this;
      }
  
      /**
       * Toggle the visibility of the progress section of the card. Only applicable if
       * the card has a progress type of
       * {@link #PROGRESS_TYPE_NORMAL}, {@link #PROGRESS_TYPE_INDETERMINATE} or
       * {@link #PROGRESS_TYPE_LABEL}.
       * @param isVisible
       * @return
       */
      public Card setProgressVisibility(boolean isVisible) {
          if (mCardProgress.progressView == null) {
              return this; // Card does not have progress
          }
          mCardProgress.progressView.setVisibility(isVisible ? View.VISIBLE : View.GONE);
  
          return this;
      }
  
      /**
       * Adds an action to this card during build time.
       *
       * @param label
       * @param id
       * @param type
       */
      private void addAction(String label, int id, int type) {
          CardAction cardAction = new CardAction();
          cardAction.label = label;
          cardAction.id = id;
          cardAction.type = type;
          mCardActions.add(cardAction);
      }
  
      /**
       * Toggles the visibility of a card action.
       * @param actionId
       * @param isVisible
       * @return
       */
      public Card setActionVisibility(int actionId, boolean isVisible) {
          int visibilityFlag = isVisible ? View.VISIBLE : View.GONE;
          for (CardAction action : mCardActions) {
              if (action.id == actionId && action.actionView != null) {
                  action.actionView.setVisibility(visibilityFlag);
              }
          }
          return this;
      }
  
      /**
       * Toggles visibility of the action area of this Card through an animation.
       * @param isVisible
       * @return
       */
      public Card setActionAreaVisibility(boolean isVisible) {
          if (mActionAreaView == null) {
              return this; // Card does not have an action area
          }
  
          if (isVisible) {
              // Show the action area
              mActionAreaView.setVisibility(View.VISIBLE);
              mActionAreaView.setPivotY(0.f);
              mActionAreaView.setPivotX(mCardView.getWidth() / 2.f);
              mActionAreaView.setAlpha(0.5f);
              mActionAreaView.setRotationX(-90.f);
              mActionAreaView.animate().rotationX(0.f).alpha(1.f).setDuration(400);
          } else {
              // Hide the action area
              mActionAreaView.setPivotY(0.f);
              mActionAreaView.setPivotX(mCardView.getWidth() / 2.f);
              mActionAreaView.animate().rotationX(-90.f).alpha(0.f).setDuration(400).setListener(
                      new AnimatorListenerAdapter() {
                          @Override
                          public void onAnimationEnd(Animator animation) {
                              mActionAreaView.setVisibility(View.GONE);
                          }
                      });
          }
          return this;
      }
  
  
      /**
       * Creates a shallow clone of the card.  Shallow means all values are present, but no views.
       * This is useful for saving/restoring in the case of configuration changes, like screen
       * rotation.
       *
       * @return A shallow clone of the card instance
       */
      public Card createShallowClone() {
          Card cloneCard = new Card();
  
          // Outer card values
          cloneCard.mTitle = mTitle;
          cloneCard.mDescription = mDescription;
          cloneCard.mTag = mTag;
          cloneCard.mLayoutId = mLayoutId;
          cloneCard.mCardState = mCardState;
  
          // Progress
          if (mCardProgress != null) {
              cloneCard.mCardProgress = mCardProgress.createShallowClone();
          }
  
          // Actions
          for (CardAction action : mCardActions) {
              cloneCard.mCardActions.add(action.createShallowClone());
          }
  
          return cloneCard;
      }
  
  
      /**
       * Prepare the card to be stored for configuration change.
       */
      public void prepareForConfigurationChange() {
          // Null out views.
          mCardView = null;
          for (CardAction action : mCardActions) {
              action.actionView = null;
          }
          mCardProgress.progressView = null;
      }
  
      /**
       * Creates a new {@link #Card}.
       */
      public static class Builder {
          private Card mCard;
  
          /**
           * Instantiate the builder with data from a shallow clone.
           * @param listener
           * @param card
           * @see Card#createShallowClone()
           */
          protected Builder(OnCardClickListener listener, Card card) {
              mCard = card;
              mCard.mClickListener = listener;
          }
  
          /**
           * Instantiate the builder with the tag of the card.
           * @param listener
           * @param tag
           */
          public Builder(OnCardClickListener listener, String tag) {
              mCard = new Card();
              mCard.mTag = tag;
              mCard.mClickListener = listener;
          }
  
          public Builder setTitle(String title) {
              mCard.mTitle = title;
              return this;
          }
  
          public Builder setDescription(String desc) {
              mCard.mDescription = desc;
              return this;
          }
  
          /**
           * Add an action.
           * The type describes how this action will be displayed. Accepted values are
           * {@link #ACTION_NEUTRAL}, {@link #ACTION_POSITIVE} or {@link #ACTION_NEGATIVE}.
           *
           * @param label The text to display for this action
           * @param id Identifier for this action, supplied in the click listener
           * @param type UI style of action
           * @return
           */
          public Builder addAction(String label, int id, int type) {
              mCard.addAction(label, id, type);
              return this;
          }
  
          /**
           * Override the default layout.
           * The referenced layout file has to contain the same identifiers as defined in the default
           * layout configuration.
           * @param layout
           * @return
           * @see R.layout.card
           */
          public Builder setLayout(int layout) {
              mCard.mLayoutId = layout;
              return this;
          }
  
          /**
           * Set the type of progress bar to display.
           * Accepted values are:
           * <ul>
           *     <li>{@link #PROGRESS_TYPE_NO_PROGRESS} disables the progress indicator</li>
           *     <li>{@link #PROGRESS_TYPE_NORMAL} 
           *     displays a standard, linear progress indicator.</li>
           *     <li>{@link #PROGRESS_TYPE_INDETERMINATE} displays an indeterminate (infite) progress
           *     indicator.</li>
           *     <li>{@link #PROGRESS_TYPE_LABEL} only displays a label text in the progress area
           *     of the card.</li>
           * </ul>
           *
           * @param progressType
           * @return
           */
          public Builder setProgressType(int progressType) {
              mCard.setProgressType(progressType);
              return this;
          }
  
          public Builder setProgressLabel(String label) {
              // ensure the progress layout has been initialized, use 'no progress' by default
              if (mCard.mCardProgress == null) {
                  mCard.setProgressType(PROGRESS_TYPE_NO_PROGRESS);
              }
              mCard.mCardProgress.label = label;
              return this;
          }
  
          public Builder setProgressMaxValue(int maxValue) {
              // ensure the progress layout has been initialized, use 'no progress' by default
              if (mCard.mCardProgress == null) {
                  mCard.setProgressType(PROGRESS_TYPE_NO_PROGRESS);
              }
              mCard.mCardProgress.maxValue = maxValue;
              return this;
          }
  
          public Builder setStatus(int status) {
              mCard.setState(status);
              return this;
          }
  
          public Card build(Activity activity) {
              LayoutInflater inflater = activity.getLayoutInflater();
              // Inflating the card.
              ViewGroup cardView = (ViewGroup) inflater.inflate(mCard.mLayoutId,
                      (ViewGroup) activity.findViewById(R.id.card_stream), false);
  
              // Check that the layout contains a TextView with the card_title id
              View viewTitle = cardView.findViewById(R.id.card_title);
              if (mCard.mTitle != null && viewTitle != null) {
                  mCard.mTitleView = (TextView) viewTitle;
                  mCard.mTitleView.setText(mCard.mTitle);
              } else if (viewTitle != null) {
                  viewTitle.setVisibility(View.GONE);
              }
  
              // Check that the layout contains a TextView with the card_content id
              View viewDesc = cardView.findViewById(R.id.card_content);
              if (mCard.mDescription != null && viewDesc != null) {
                  mCard.mDescView = (TextView) viewDesc;
                  mCard.mDescView.setText(mCard.mDescription);
              } else if (viewDesc != null) {
                  cardView.findViewById(R.id.card_content).setVisibility(View.GONE);
              }
  
  
              ViewGroup actionArea = (ViewGroup) cardView.findViewById(R.id.card_actionarea);
  
              // Inflate Progress
              initializeProgressView(inflater, actionArea);
  
              // Inflate all action views.
              initializeActionViews(inflater, cardView, actionArea);
  
              mCard.mCardView = cardView;
              mCard.mOverlayView = cardView.findViewById(R.id.card_overlay);
  
              return mCard;
          }
  
          /**
           * Initialize data from the given card.
           * @param card
           * @return
           * @see Card#createShallowClone()
           */
          public Builder cloneFromCard(Card card) {
              mCard = card.createShallowClone();
              return this;
          }
  
          /**
           * Build the action views by inflating the appropriate layouts and setting the text and 
           * values.
           * @param inflater
           * @param cardView
           * @param actionArea
           */
          private void initializeActionViews(LayoutInflater inflater, ViewGroup cardView,
                                             ViewGroup actionArea) {
              if (!mCard.mCardActions.isEmpty()) {
                  // Set action area to visible only when actions are visible
                  actionArea.setVisibility(View.VISIBLE);
                  mCard.mActionAreaView = actionArea;
              }
  
              // Inflate all card actions
              for (final CardAction action : mCard.mCardActions) {
  
                  int useActionLayout = 0;
                  switch (action.type) {
                      case Card.ACTION_POSITIVE:
                          useActionLayout = R.layout.card_button_positive;
                          break;
                      case Card.ACTION_NEGATIVE:
                          useActionLayout = R.layout.card_button_negative;
                          break;
                      case Card.ACTION_NEUTRAL:
                      default:
                          useActionLayout = R.layout.card_button_neutral;
                          break;
                  }
  
                  action.actionView = inflater.inflate(useActionLayout, actionArea, false);
                  Button actionButton = (Button) action.actionView.findViewById(R.id.card_button);
  
                  actionButton.setText(action.label);
                  actionButton.setOnClickListener(new View.OnClickListener() {
                      @Override
                      public void onClick(View v) {
                          mCard.mClickListener.onCardClick(action.id, mCard.mTag);
                      }
                  });
                  actionArea.addView(action.actionView);
              }
          }
  
          /**
           * Build the progress view into the given ViewGroup.
           *
           * @param inflater
           * @param actionArea
           */
          private void initializeProgressView(LayoutInflater inflater, ViewGroup actionArea) {
  
              // Only inflate progress layout if a progress type other than NO_PROGRESS was set.
              if (mCard.mCardProgress != null) {
                  //Setup progress card.
                  View progressView = inflater.inflate(R.layout.card_progress, actionArea, false);
                  ProgressBar progressBar = 
                          (ProgressBar) progressView.findViewById(R.id.card_progress);
                  ((TextView) progressView.findViewById(R.id.card_progress_text))
                          .setText(mCard.mCardProgress.label);
                  progressBar.setMax(mCard.mCardProgress.maxValue);
                  progressBar.setProgress(0);
                  mCard.mCardProgress.progressView = progressView;
                  mCard.mCardProgress.setProgressType(mCard.getProgressType());
                  actionArea.addView(progressView);
              }
          }
      }
  
      /**
       * Represents a clickable action, accessible from the bottom of the card.
       * Fields include the label, an ID to specify the action that was performed in the callback,
       * an action type (positive, negative, neutral), and the callback.
       */
      public class CardAction {
  
          public String label;
          public int id;
          public int type;
          public View actionView;
  
          public CardAction createShallowClone() {
              CardAction actionClone = new CardAction();
              actionClone.label = label;
              actionClone.id = id;
              actionClone.type = type;
              return actionClone;
              // Not the view.  Never the view (don't want to hold view references for
              // onConfigurationChange.
          }
  
      }
  
      /**
       * Describes the progress of a {@link Card}.
       * Three types of progress are supported:
       * <ul><li>{@link Card#PROGRESS_TYPE_NORMAL: Standard progress bar with label text</li>
       * <li>{@link Card#PROGRESS_TYPE_INDETERMINATE}: Indeterminate progress bar with label txt</li>
       * <li>{@link Card#PROGRESS_TYPE_LABEL}: Label only, no progresss bar</li>
       * </ul>
       */
      public class CardProgress {
          private int progressType = Card.PROGRESS_TYPE_NO_PROGRESS;
          private String label = "";
          private int currProgress = 0;
          private int maxValue = 100;
  
          public View progressView = null;
          private ProgressBar progressBar = null;
          private TextView progressLabel = null;
  
          public CardProgress createShallowClone() {
              CardProgress progressClone = new CardProgress();
              progressClone.label = label;
              progressClone.currProgress = currProgress;
              progressClone.maxValue = maxValue;
              progressClone.progressType = progressType;
              return progressClone;
          }
  
          /**
           * Set the progress. Only useful for the type {@link #PROGRESS_TYPE_NORMAL}.
           * @param progress
           * @see android.widget.ProgressBar#setProgress(int)
           */
          public void setProgress(int progress) {
              currProgress = progress;
              final ProgressBar bar = getProgressBar();
              if (bar != null) {
                  bar.setProgress(currProgress);
                  bar.invalidate();
              }
          }
  
          /**
           * Set the range of the progress to 0...max.
           * Only useful for the type {@link #PROGRESS_TYPE_NORMAL}.
           * @param max
           * @see android.widget.ProgressBar#setMax(int)
           */
          public void setMax(int max) {
              maxValue = max;
              final ProgressBar bar = getProgressBar();
              if (bar != null) {
                  bar.setMax(maxValue);
              }
          }
  
          /**
           * Set the label text that appears near the progress indicator.
           * @param text
           */
          public void setProgressLabel(String text) {
              label = text;
              final TextView labelView = getProgressLabel();
              if (labelView != null) {
                  labelView.setText(text);
              }
          }
  
          /**
           * Set how progress is displayed. The parameter must be one of three supported types:
           * <ul><li>{@link Card#PROGRESS_TYPE_NORMAL: Standard progress bar with label text</li>
           * <li>{@link Card#PROGRESS_TYPE_INDETERMINATE}: 
           * Indeterminate progress bar with label txt</li>
           * <li>{@link Card#PROGRESS_TYPE_LABEL}: Label only, no progresss bar</li>
           * @param type
           */
          public void setProgressType(int type) {
              progressType = type;
              if (progressView != null) {
                  switch (type) {
                      case PROGRESS_TYPE_NO_PROGRESS: {
                          progressView.setVisibility(View.GONE);
                          break;
                      }
                      case PROGRESS_TYPE_NORMAL: {
                          progressView.setVisibility(View.VISIBLE);
                          getProgressBar().setIndeterminate(false);
                          break;
                      }
                      case PROGRESS_TYPE_INDETERMINATE: {
                          progressView.setVisibility(View.VISIBLE);
                          getProgressBar().setIndeterminate(true);
                          break;
                      }
                  }
              }
          }
  
          private TextView getProgressLabel() {
              if (progressLabel != null) {
                  return progressLabel;
              } else if (progressView != null) {
                  progressLabel = (TextView) progressView.findViewById(R.id.card_progress_text);
                  return progressLabel;
              } else {
                  return null;
              }
          }
  
          private ProgressBar getProgressBar() {
              if (progressBar != null) {
                  return progressBar;
              } else if (progressView != null) {
                  progressBar = (ProgressBar) progressView.findViewById(R.id.card_progress);
                  return progressBar;
              } else {
                  return null;
              }
          }
  
      }
  }
  
  