org.gateshipone.odyssey.adapter.GenericSectionAdapter.java Source code

Java tutorial

Introduction

Here is the source code for org.gateshipone.odyssey.adapter.GenericSectionAdapter.java

Source

/*
 * Copyright (C) 2016  Hendrik Borghorst & Frederik Luetkes
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.gateshipone.odyssey.adapter;

import android.os.AsyncTask;
import android.support.v4.util.Pair;
import android.widget.SectionIndexer;

import org.gateshipone.odyssey.models.GenericModel;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public abstract class GenericSectionAdapter<T extends GenericModel> extends ScrollSpeedAdapter
        implements SectionIndexer {
    /**
     * Variables used for sectioning (fast scroll).
     */
    private final ArrayList<String> mSectionList;
    private final ArrayList<Integer> mSectionPositions;
    private final HashMap<Character, Integer> mPositionSectionMap;

    private FilterTask mFilterTask = null;

    /**
     * Abstract list with model data used for this adapter.
     */
    private List<T> mModelData;

    /**
     * Abstract list with filtered model data, this list is null if no filter is applied.
     * <p>
     * If not null this list will be used as the model for the adapter.
     */
    private final List<T> mFilteredModelData;

    /**
     * The currently applied filter string.
     */
    private String mFilter;

    public GenericSectionAdapter() {
        super();

        mSectionList = new ArrayList<>();
        mSectionPositions = new ArrayList<>();
        mPositionSectionMap = new HashMap<>();

        mModelData = new ArrayList<>();

        mFilteredModelData = new ArrayList<>();

        mFilter = "";
    }

    /**
     * Swaps the model of this adapter. This sets the dataset on which the
     * adapter creates the GridItems. This should generally be safe to call.
     * Clears old section data and model data and recreates sectionScrolling
     * data.
     *
     * @param data Actual model data
     */
    public void swapModel(List<T> data) {
        if (data == null) {
            mModelData.clear();
        } else {
            mModelData.clear();
            mModelData.addAll(data);
        }
        synchronized (mFilteredModelData) {
            mFilteredModelData.clear();
        }

        setScrollSpeed(0);

        if (mFilter.isEmpty()) {
            // create sectionlist for fastscrolling
            createSections();

            notifyDataSetChanged();
        } else {
            // Refilter the new data
            if (mFilterTask != null) {
                mFilterTask.cancel(true);
            }
            mFilterTask = new FilterTask();
            mFilterTask.execute(mFilter);
        }
    }

    /**
     * Looks up the position(index) of a given section(index)
     *
     * @param sectionIndex Section to get the ListView/GridView position for
     * @return The item position of this section start.
     */
    @Override
    public int getPositionForSection(int sectionIndex) {
        return mSectionPositions.get(sectionIndex);
    }

    /**
     * Reverse lookup of a section for a given position
     *
     * @param pos Position to get the section for
     * @return Section (index) for the items position
     */
    @Override
    public int getSectionForPosition(int pos) {

        String sectionTitle;

        synchronized (mFilteredModelData) {
            if (!mFilteredModelData.isEmpty()) {
                sectionTitle = mFilteredModelData.get(pos).getSectionTitle();
            } else {
                sectionTitle = mModelData.get(pos).getSectionTitle();
            }
        }

        char itemSection;
        if (sectionTitle.length() > 0) {
            itemSection = sectionTitle.toUpperCase().charAt(0);
        } else {
            itemSection = ' ';
        }

        if (mPositionSectionMap.containsKey(itemSection)) {
            return mPositionSectionMap.get(itemSection);
        }
        return 0;
    }

    /**
     * @return A list of all available sections
     */
    @Override
    public Object[] getSections() {
        return mSectionList.toArray();
    }

    /**
     * @return The length of the model data of this adapter.
     */
    @Override
    public int getCount() {
        synchronized (mFilteredModelData) {
            if (!mFilteredModelData.isEmpty() || !mFilter.isEmpty()) {
                return mFilteredModelData.size();
            } else {
                return mModelData.size();
            }
        }
    }

    /**
     * Simple getter for the model data.
     *
     * @param position Index of the item to get. No check for boundaries here.
     * @return The item at index position.
     */
    @Override
    public Object getItem(int position) {
        synchronized (mFilteredModelData) {
            if (!mFilteredModelData.isEmpty() || !mFilter.isEmpty()) {
                return mFilteredModelData.get(position);
            } else {
                return mModelData.get(position);
            }
        }
    }

    /**
     * Simple position->id mapping here.
     *
     * @param position Position to get the id from
     * @return The id (position)
     */
    @Override
    public long getItemId(int position) {
        return position;
    }

    /**
     * Apply the given string as a filter to the model.
     *
     * @param filter The filter string
     */
    public void applyFilter(String filter) {
        if (!filter.equals(mFilter)) {
            mFilter = filter;
        }

        if (mFilterTask != null) {
            mFilterTask.cancel(true);
        }
        mFilterTask = new FilterTask();
        mFilterTask.execute(filter);
    }

    /**
     * Remove a previous filter.
     * <p>
     * This method will clear the filtered model data.
     */
    public void removeFilter() {
        synchronized (mFilteredModelData) {
            mFilteredModelData.clear();
        }
        mFilter = "";

        if (mFilterTask != null) {
            mFilterTask.cancel(true);
            mFilterTask = null;
        }

        createSections();
        setScrollSpeed(0);
        notifyDataSetChanged();
    }

    /**
     * Create the section list for the current model for fast scrolling.
     */
    private void createSections() {
        mSectionList.clear();
        mSectionPositions.clear();
        mPositionSectionMap.clear();

        if (getCount() > 0) {
            GenericModel currentModel = (GenericModel) getItem(0);

            char lastSection;
            if (currentModel.getSectionTitle().length() > 0) {
                lastSection = currentModel.getSectionTitle().toUpperCase().charAt(0);
            } else {
                lastSection = ' ';
            }

            mSectionList.add("" + lastSection);
            mSectionPositions.add(0);
            mPositionSectionMap.put(lastSection, mSectionList.size() - 1);

            for (int i = 1; i < getCount(); i++) {

                currentModel = (GenericModel) getItem(i);

                char currentSection;
                if (currentModel.getSectionTitle().length() > 0) {
                    currentSection = currentModel.getSectionTitle().toUpperCase().charAt(0);
                } else {
                    currentSection = ' ';
                }

                if (lastSection != currentSection) {
                    mSectionList.add("" + currentSection);

                    lastSection = currentSection;
                    mSectionPositions.add(i);
                    mPositionSectionMap.put(currentSection, mSectionList.size() - 1);
                }

            }
        }
    }

    /**
     * Async task to perform the filtering of the current model data with the given filter string.
     * <p>
     * The filter will be applied to the section title of the model data.
     */
    private class FilterTask extends AsyncTask<String, Void, Pair<String, List<T>>> {

        @Override
        protected Pair<String, List<T>> doInBackground(String... params) {
            List<T> result = new ArrayList<>();

            String filter = params[0];

            for (T data : mModelData) {
                // Check if task was cancelled from the outside.
                if (isCancelled()) {
                    result.clear();
                    return new Pair<>(filter, result);
                }
                if (data.getSectionTitle().toLowerCase().contains(filter.toLowerCase())) {
                    result.add(data);
                }
            }

            return new Pair<>(filter, result);
        }

        protected void onPostExecute(Pair<String, List<T>> result) {
            if (!isCancelled() && mFilter.equals(result.first)) {
                mFilteredModelData.clear();

                mFilteredModelData.addAll(result.second);

                createSections();

                notifyDataSetChanged();
                if (mFilterTask == this) {
                    mFilterTask = null;
                }
            }
        }
    }
}