Make fast scroll sort-aware

This commit is contained in:
Адонай Элохим 2016-04-02 01:23:17 +03:00
parent 6b8b15acbe
commit 43ccf6de4d

View File

@ -33,7 +33,6 @@ import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
@ -43,6 +42,7 @@ import android.widget.ImageView;
import android.widget.SectionIndexer;
import android.widget.TextView;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.ArrayList;
@ -60,10 +60,10 @@ import java.lang.StringBuilder;
* See getLimiter and setLimiter for details.
*/
public class MediaAdapter
extends BaseAdapter
implements LibraryAdapter
, View.OnClickListener
, SectionIndexer
extends BaseAdapter
implements LibraryAdapter
, View.OnClickListener
, SectionIndexer
{
private static final Pattern SPACE_SPLIT = Pattern.compile("\\s+");
@ -148,7 +148,9 @@ public class MediaAdapter
* Setting this to MediaUtils.TYPE_INVALID disables cover artwork
*/
private int mCoverCacheType;
/**
* Alphabet to be used for {@link SectionIndexer}. Populated in {@link #buildAlphabet()}.
*/
private List<SectionIndex> mAlphabet = new ArrayList<>(512);
/**
@ -240,6 +242,23 @@ public class MediaAdapter
}
}
/**
* Returns first sort column for this adapter. Ensure {@link #mSortMode} is correctly set
* prior to calling this.
*
* @return string representing sort column to be used in projection.
* If the column is binary, returns its human-readable counterpart instead.
*/
private String getFirstSortColumn() {
int mode = mSortMode < 0 ? ~mSortMode : mSortMode; // get current sort mode
String column = SPACE_SPLIT.split(mSortValues[mode])[0];
if(column.endsWith("_key")) { // we want human-readable string, not machine-composed
column = column.substring(0, column.length() - 4);
}
return column;
}
/**
* Set whether or not the expander button should be shown in each row.
* Defaults to true for playlist adapter and false for all others.
@ -286,8 +305,12 @@ public class MediaAdapter
String sortStringRaw = mSortValues[mode];
String[] enrichedProjection;
// Magic sort mode: sort by playcount
if (sortStringRaw == SORT_MAGIC_PLAYCOUNT) {
// special case, no explicit column to sort
enrichedProjection = projection;
ArrayList<Long> topSongs = (new PlayCountsHelper(mContext)).getTopSongs(4096);
int sortWeight = -1 * topSongs.size(); // Sort mode is actually reversed (default: mostplayed -> leastplayed)
@ -298,10 +321,16 @@ public class MediaAdapter
}
sb.append(" ELSE 0 END %1s");
sortStringRaw = sb.toString();
} else if (returnSongs && mType != MediaUtils.TYPE_SONG) {
// We are in a non-song adapter but requested to return songs - sorting
// can only be done by using the adapters default sort mode :-(
sortStringRaw = mSongSort;
} else {
// enrich projection with sort column to build alphabet later
enrichedProjection = Arrays.copyOf(projection, projection.length + 1);
enrichedProjection[projection.length] = getFirstSortColumn();
if (returnSongs && mType != MediaUtils.TYPE_SONG) {
// We are in a non-song adapter but requested to return songs - sorting
// can only be done by using the adapters default sort mode :-(
sortStringRaw = mSongSort;
}
}
String sort = String.format(sortStringRaw, sortDir);
@ -351,19 +380,19 @@ public class MediaAdapter
QueryTask query;
if(mType == MediaUtils.TYPE_GENRE && !returnSongs) {
query = MediaUtils.buildGenreExcludeEmptyQuery(projection, selection.toString(),
query = MediaUtils.buildGenreExcludeEmptyQuery(enrichedProjection, selection.toString(),
selectionArgs, sort);
} else if (limiter != null && limiter.type == MediaUtils.TYPE_GENRE) {
// Genre is not standard metadata for MediaStore.Audio.Media.
// We have to query it through a separate provider. : /
query = MediaUtils.buildGenreQuery((Long)limiter.data, projection, selection.toString(), selectionArgs, sort, mType, returnSongs);
query = MediaUtils.buildGenreQuery((Long)limiter.data, enrichedProjection, selection.toString(), selectionArgs, sort, mType, returnSongs);
} else {
if (limiter != null) {
if (selection.length() != 0)
selection.append(" AND ");
selection.append(limiter.data);
}
query = new QueryTask(mStore, projection, selection.toString(), selectionArgs, sort);
query = new QueryTask(mStore, enrichedProjection, selection.toString(), selectionArgs, sort);
if (returnSongs) // force query on song provider as we are requested to return songs
query.uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
@ -643,59 +672,105 @@ public class MediaAdapter
return true;
}
private class SectionIndex
/**
* Helper class for building an alphabet.
* Contains a hint that is shown in fast scroll thumb popup and a position where this hint
* has appeared first.
*/
private class SectionIndex
{
public SectionIndex(char letter, int position) {
this.letter = letter;
public SectionIndex(Object hint, int position) {
this.hint = hint;
this.position = position;
}
private char letter;
private Object hint;
private int position;
@Override
public String toString() {
return String.valueOf(letter);
return String.valueOf(hint);
}
}
private void buildAlphabet()
/**
* Build alphabet for fast-scroller. Detects automatically whether we're sorting
* on string-type (e.g. title or album) or integer type (e.g. year).
*
* <p/>Alphabet building is only performed if applicable, i.e. magic playcount sort
* or sort by date added will yield no results as the section hints would not be
* human-readable.
*
* <p/>Note: This clears alphabet in case current cursor is invalid.
*/
private void buildAlphabet()
{
mAlphabet.clear();
Cursor cursor = mCursor;
if(cursor == null)
if(cursor == null || cursor.getCount() == 0) {
return;
}
String columnName = getFirstSortColumn();
int sortColumnIndex = cursor.getColumnIndex(columnName);
if(sortColumnIndex <= 0) {
// either projection doesn't contain this column
// or the column is _id (e.g. sort by date added),
// no point in building
return;
}
cursor.moveToFirst();
char lastKnown = 0;
Object lastKnown = null;
Object next;
do {
String title = cursor.getString(2);
if(!TextUtils.isEmpty(title)) {
Character next = title.charAt(0);
if(next != lastKnown) { // new char
mAlphabet.add(new SectionIndex(next, cursor.getPosition()));
lastKnown = next;
}
if(cursor.isNull(sortColumnIndex))
continue;
int type = cursor.getType(sortColumnIndex);
switch (type) {
case Cursor.FIELD_TYPE_INTEGER:
next = cursor.getInt(sortColumnIndex);
break;
case Cursor.FIELD_TYPE_STRING:
next = cursor.getString(sortColumnIndex).charAt(0);
break;
default:
continue;
}
if (!next.equals(lastKnown)) { // new char
mAlphabet.add(new SectionIndex(next, cursor.getPosition()));
lastKnown = next;
}
} while (cursor.moveToNext());
}
@Override
public Object[] getSections()
public Object[] getSections()
{
return mAlphabet.toArray(new SectionIndex[mAlphabet.size()]);
}
@Override
public int getPositionForSection(int sectionIndex)
public int getPositionForSection(int sectionIndex)
{
// clip to start
if(sectionIndex < 0) {
return 0;
}
// clip to end
if(sectionIndex >= mAlphabet.size()) {
return mCursor.getCount() - 1;
}
return mAlphabet.get(sectionIndex).position;
}
@Override
public int getSectionForPosition(int position)
public int getSectionForPosition(int position)
{
for(int i = 0; i < mAlphabet.size(); ++i) {
if(mAlphabet.get(i).position > position)