SectionListAdapter.java

/*
 * Copyright (C) 2003-2014 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.widget;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.exoplatform.R;
import org.exoplatform.model.SocialActivityInfo;
import org.exoplatform.utils.SocialActivityUtil;

import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.TextView;

/**
 * Adapter for sections.
 */
public class SectionListAdapter extends BaseAdapter implements ListAdapter, OnItemClickListener {
  private final DataSetObserver       dataSetObserver     = new DataSetObserver() {
                                                            @Override
                                                            public void onChanged() {
                                                              super.onChanged();
                                                              updateSessionCache();
                                                            }

                                                            @Override
                                                            public void onInvalidated() {
                                                              super.onInvalidated();
                                                              updateSessionCache();
                                                            };
                                                          };

  /**
   * The list of items, as a ListAdapter.
   */
  private final ListAdapter           linkedAdapter;

  /**
   * Map the position of each section in the list with the name of this section
   */
  private final Map<Integer, String>  sectionPositions    = new LinkedHashMap<Integer, String>();

  /**
   * Map the position of each item after adding the sections with the actual position in the adapter
   */
  private final Map<Integer, Integer> itemPositions       = new LinkedHashMap<Integer, Integer>();

  private final Map<View, String>     currentViewSections = new HashMap<View, String>();

  /**
   * Returns the number of types of Views that will be created by getView(int, View, ViewGroup).
   */
  private int                         viewTypeCount;

  protected final LayoutInflater      inflater;

  /**
   * The fixed section header at the top-right of the list
   */
  private View                        transparentSectionView;

  private OnItemClickListener         linkedListener;

  private Context                     mContext;

  public SectionListAdapter(Context context,
                            final LayoutInflater inflater,
                            final ListAdapter linkedAdapter) {
    mContext = context;
    this.linkedAdapter = linkedAdapter;
    this.inflater = inflater;
    linkedAdapter.registerDataSetObserver(dataSetObserver);
    updateSessionCache();
  }

  private boolean isTheSame(final String previousSection, final String newSection) {
    if (previousSection == null) {
      return newSection == null;
    } else {
      return previousSection.equals(newSection);
    }
  }

  private synchronized void updateSessionCache() {
    int currentPosition = 0;
    sectionPositions.clear();
    itemPositions.clear();
    // The number of view types in the adapter + 1 for the section headers.
    viewTypeCount = linkedAdapter.getViewTypeCount() + 1;
    String currentSection = null;
    final int count = linkedAdapter.getCount();
    String section = null;
    for (int i = 0; i < count; i++) {
      final SocialActivityInfo item = (SocialActivityInfo) linkedAdapter.getItem(i);
      // a label that indicates when this activity was created (approximatively)
      section = SocialActivityUtil.getActivityStreamHeader(mContext,
        item.getUpdatedTime() != 0 ? item.getUpdatedTime() : item.getPostedTime());
      if (!isTheSame(currentSection, section)) {
    	// we're entering a new section (activities were created at a different date)
        sectionPositions.put(currentPosition, section);
        currentSection = section;
        currentPosition++;
      }
      itemPositions.put(currentPosition, i);
      currentPosition++;
    }
  }

  @Override
  public synchronized int getCount() {
    return sectionPositions.size() + itemPositions.size();
  }

  @Override
  public synchronized Object getItem(final int position) {
    if (isSection(position)) {
      return sectionPositions.get(position);
    } else {
      final int linkedItemPosition = getLinkedPosition(position);
      return linkedAdapter.getItem(linkedItemPosition);
    }
  }

  public synchronized boolean isSection(final int position) {
    return sectionPositions.containsKey(position);
  }

  public synchronized String getSectionName(final int position) {
    if (isSection(position)) {
      return sectionPositions.get(position);
    } else {
      return null;
    }
  }

  public int getSectionPosition(String sectionName) {
    if (sectionPositions.containsValue(sectionName)) {
      return getKey(sectionPositions, sectionName);
    } else
      return 0;
  }

  private int getKey(Map<Integer, String> map, String value) {
    for (Iterator i = map.keySet().iterator(); i.hasNext();) {
      int key = (Integer) i.next();
      if (map.get(key).equals(value)) {
        return key;
      }
    }
    return 0;
  }

  @Override
  public long getItemId(final int position) {
    if (isSection(position)) {
      return sectionPositions.get(position).hashCode();
    } else {
      return linkedAdapter.getItemId(getLinkedPosition(position));
    }
  }

  protected Integer getLinkedPosition(final int position) {
    return itemPositions.get(position);
  }

  @Override
  public int getItemViewType(final int position) {
    if (isSection(position)) {
      return viewTypeCount - 1;
    }
    return linkedAdapter.getItemViewType(getLinkedPosition(position));
  }

  private View getSectionView(final View convertView, final String section) {
    View theView = convertView;
    if (theView == null) {
      theView = createNewSectionView();
    }
    setSectionText(section, theView);
    replaceSectionViewsInMaps(section, theView);
    return theView;
  }

  /**
   * Writes <code>section</code> on the view <code>sectionView</code>.
   * @param section The label to write for this section.
   * @param sectionView The view on which the label is set.
   */
  protected void setSectionText(final String section, final View sectionView) {
    final TextView textView = (TextView) sectionView.findViewById(R.id.textView_Section_Title);
    textView.setText(section);
    if (section.equalsIgnoreCase(mContext.getString(R.string.Today))) {
      textView.setBackgroundResource(R.drawable.social_activity_browse_header_highlighted_bg);
    } else {
      textView.setBackgroundResource(R.drawable.social_activity_browse_header_normal_bg);
      textView.setTextColor(Color.rgb(59, 59, 59));
    }
  }

  protected synchronized void replaceSectionViewsInMaps(final String section, final View theView) {
    if (currentViewSections.containsKey(theView)) {
      currentViewSections.remove(theView);
    }
    currentViewSections.put(theView, section);
  }

  protected View createNewSectionView() {
    return inflater.inflate(R.layout.activityheadersection, null);
  }

  @Override
  public View getView(final int position, final View convertView, final ViewGroup parent) {
    if (isSection(position)) {
      return getSectionView(convertView, sectionPositions.get(position));
    }
    return linkedAdapter.getView(getLinkedPosition(position), convertView, parent);
  }

  @Override
  public int getViewTypeCount() {
    return viewTypeCount;
  }

  @Override
  public boolean hasStableIds() {
    return linkedAdapter.hasStableIds();
  }

  @Override
  public boolean isEmpty() {
    return linkedAdapter.isEmpty();
  }

  @Override
  public void registerDataSetObserver(final DataSetObserver observer) {
    linkedAdapter.registerDataSetObserver(observer);
  }

  @Override
  public void unregisterDataSetObserver(final DataSetObserver observer) {
    linkedAdapter.unregisterDataSetObserver(observer);
  }

  @Override
  public boolean areAllItemsEnabled() {
    return linkedAdapter.areAllItemsEnabled();
  }

  @Override
  public boolean isEnabled(final int position) {
    if (isSection(position)) {
      return true;
    }
    return linkedAdapter.isEnabled(getLinkedPosition(position));
  }

  /**
   * Hide the section header when it reaches the top of the list.
   * @param firstVisibleItem The position of the item currently at the top of the list.
   */
  public void makeSectionInvisibleIfFirstInList(final int firstVisibleItem) {
    final String section = getSectionName(firstVisibleItem);
    // only make invisible the first section with that name in case there
    // are more with the same name
    boolean alreadySetFirstSectionIvisible = false;
    for (final Entry<View, String> itemView : currentViewSections.entrySet()) {
      if (itemView.getValue().equals(section) && !alreadySetFirstSectionIvisible) {
        itemView.getKey().setVisibility(View.INVISIBLE);
        alreadySetFirstSectionIvisible = true;
      } else {
        itemView.getKey().setVisibility(View.VISIBLE);
      }
    }
    for (final Entry<Integer, String> entry : sectionPositions.entrySet()) {
      if (entry.getKey() > firstVisibleItem + 1) {
        break;
      }
      setSectionText(entry.getValue(), getTransparentSectionView());
    }
  }

  /**
   * @return The fixed section header view. Inflate it first if it doesn't exist.
   */
  public synchronized View getTransparentSectionView() {
    if (transparentSectionView == null) {
      transparentSectionView = createNewSectionView();
    }
    return transparentSectionView;
  }

  protected void sectionClicked(final String section) {
    // do nothing
  }

  @Override
  public void onItemClick(final AdapterView<?> parent,
                          final View view,
                          final int position,
                          final long id) {
    if (isSection(position)) {
      sectionClicked(getSectionName(position));
    } else if (linkedListener != null) {
      linkedListener.onItemClick(parent, view, getLinkedPosition(position), id);
    }
  }

  public void setOnItemClickListener(final OnItemClickListener linkedListener) {
    this.linkedListener = linkedListener;
  }
}