Implement sorting for files tab (#577)

* Added new translation strings for file sort dialog

* Implemented sorting for the file system adapter

With new abstract class SortableAdapter as new base class for FileSystemAdapter
and MediaAdapter.

Usable sorting modes are:
 - filename
 - file size
 - file modification time
 - file extension
* reversed

Closes #305

* Code improvements for sorted file system adapter
This commit is contained in:
Alex Bikadorov 2017-04-08 11:27:54 +02:00 committed by Adrian Ulrich
parent 55a5ff6aa0
commit 10878de963
7 changed files with 196 additions and 81 deletions

View File

@ -121,6 +121,11 @@ THE SOFTWARE.
<string name="genre">Genre</string>
<string name="folder">Folder</string>
<string name="filename">Filename</string>
<string name="file_size">File size</string>
<string name="file_time">Modification time</string>
<string name="extension">Extension</string>
<string name="unknown">Unknown</string>
<string name="play_all">Play all</string>
<string name="enqueue_all">Enqueue all</string>

View File

@ -31,9 +31,6 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
@ -46,12 +43,38 @@ import java.util.regex.Pattern;
* is set through a {@link Limiter} and rows are displayed using MediaViews.
*/
public class FileSystemAdapter
extends BaseAdapter
extends SortableAdapter
implements LibraryAdapter
{
private static final Pattern SPACE_SPLIT = Pattern.compile("\\s+");
private static final Pattern FILE_SEPARATOR = Pattern.compile(File.separator);
/**
* Sort by filename.
*/
private static final int SORT_NAME = 0;
/**
* Sort by file size.
*/
private static final int SORT_SIZE = 1;
/**
* Sort by file modification time.
*/
private static final int SORT_TIME = 2;
/**
* Sort by file extension.
*/
private static final int SORT_EXT = 3;
/**
* The IDs of human-readable descriptions for each sort mode.
* Must be consistent with SORT_* fields.
*/
private static final int[] SORT_RES_IDS = new int[] {
R.string.filename,
R.string.file_size,
R.string.file_time,
R.string.extension };
/**
* The owner LibraryActivity.
*/
@ -96,7 +119,7 @@ public class FileSystemAdapter
}
};
/**
* Sorts folders before files first, then sorts alphabetically by name.
* Sorts folders before files first, then sorts by current sort mode.
*/
private final Comparator<File> mFileComparator = new Comparator<File>() {
@Override
@ -105,7 +128,27 @@ public class FileSystemAdapter
boolean aIsFolder = a.isDirectory();
boolean bIsFolder = b.isDirectory();
if (bIsFolder == aIsFolder) {
return a.getName().compareToIgnoreCase(b.getName());
int mode = aIsFolder ? SORT_NAME : getSortModeIndex();
int order;
switch (mode) {
case SORT_SIZE:
order = Long.valueOf(a.length()).compareTo(Long.valueOf(b.length()));
break;
case SORT_TIME:
order = Long.valueOf(a.lastModified())
.compareTo(Long.valueOf(b.lastModified()));
break;
case SORT_EXT:
order = FileUtils.getFileExtension(a.getName())
.compareToIgnoreCase(FileUtils.getFileExtension(b.getName()));
break;
case SORT_NAME:
order = a.getName().compareToIgnoreCase(b.getName());
break;
default:
throw new IllegalArgumentException("Invalid sort mode: " + mode);
}
return (isSortDescending() ? -1 : 1) * order;
} else if (bIsFolder) {
return 1;
}
@ -136,6 +179,7 @@ public class FileSystemAdapter
limiter = buildHomeLimiter(activity);
}
setLimiter(limiter);
mSortEntries = SORT_RES_IDS;
}
@Override
@ -324,6 +368,16 @@ public class FileSystemAdapter
return intent;
}
@Override
public int getDefaultSortMode() {
return SORT_NAME;
}
@Override
public String getSortSettingsKey() {
return "sort_filesystem";
}
/**
* Returns all songs represented by this adapter.
* Note that this will do a recursive query!

View File

@ -95,4 +95,13 @@ public class FileUtils {
String folder = prefs.getString(PrefKeys.FILESYSTEM_BROWSE_START, PrefDefaults.FILESYSTEM_BROWSE_START);
return new File( folder.equals("") ? Environment.getExternalStorageDirectory().getAbsolutePath() : folder );
}
/**
* Return the file extension for a given filename (including dot).
* Empty string is returned if there is no extension.
*/
public static String getFileExtension(String filename) {
int index = filename.lastIndexOf(".");
return index > 0 ? filename.substring(index) : "";
}
}

View File

@ -757,9 +757,9 @@ public class LibraryActivity
public boolean onPrepareOptionsMenu(Menu menu)
{
LibraryAdapter adapter = mCurrentAdapter;
boolean isLibraryAdapter = (adapter != null && adapter.getMediaType() != MediaUtils.TYPE_FILE);
menu.findItem(MENU_GO_HOME).setVisible(!isLibraryAdapter);
menu.findItem(MENU_SORT).setEnabled(isLibraryAdapter);
menu.findItem(MENU_GO_HOME).setVisible(
adapter != null && adapter.getMediaType() == MediaUtils.TYPE_FILE);
menu.findItem(MENU_SORT).setEnabled(adapter != null);
return super.onPrepareOptionsMenu(menu);
}
@ -778,7 +778,7 @@ public class LibraryActivity
updateLimiterViews();
break;
case MENU_SORT: {
MediaAdapter adapter = (MediaAdapter)mCurrentAdapter;
SortableAdapter adapter = (SortableAdapter)mCurrentAdapter;
LinearLayout header = (LinearLayout)getLayoutInflater().inflate(R.layout.sort_dialog, null);
CheckBox reverseSort = (CheckBox)header.findViewById(R.id.reverse_sort);
@ -789,11 +789,8 @@ public class LibraryActivity
items[i] = res.getString(itemIds[i]);
}
int mode = adapter.getSortMode();
if (mode < 0) {
mode =~ mode;
reverseSort.setChecked(true);
}
int mode = adapter.getSortModeIndex();
reverseSort.setChecked(adapter.isSortDescending());
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.sort_by);

View File

@ -31,10 +31,8 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
@ -42,10 +40,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.util.LruCache;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.LinearLayout;
import java.util.Arrays;
import java.util.ArrayList;
@ -352,8 +347,8 @@ public class LibraryPagerAdapter
mHeaderViews.add(header);
}
view.setAdapter(adapter);
if (type != MediaUtils.TYPE_FILE)
loadSortOrder((MediaAdapter)adapter);
if (adapter instanceof SortableAdapter)
loadSortOrder((SortableAdapter)adapter);
adapter.setFilter(mFilter);
@ -704,9 +699,9 @@ public class LibraryPagerAdapter
break;
}
case MSG_SAVE_SORT: {
MediaAdapter adapter = (MediaAdapter)message.obj;
SortableAdapter adapter = (SortableAdapter)message.obj;
SharedPreferences.Editor editor = PlaybackService.getSettings(mActivity).edit();
editor.putInt(String.format("sort_%d_%d", adapter.getMediaType(), adapter.getLimiterType()), adapter.getSortMode());
editor.putInt(adapter.getSortSettingsKey(), adapter.getSortMode());
editor.apply();
break;
}
@ -795,9 +790,9 @@ public class LibraryPagerAdapter
*
* @param adapter The adapter to load for.
*/
public void loadSortOrder(MediaAdapter adapter)
public void loadSortOrder(SortableAdapter adapter)
{
String key = String.format("sort_%d_%d", adapter.getMediaType(), adapter.getLimiterType());
String key = adapter.getSortSettingsKey();
int def = adapter.getDefaultSortMode();
int sort = PlaybackService.getSettings(mActivity).getInt(key, def);
adapter.setSortMode(sort);
@ -813,12 +808,12 @@ public class LibraryPagerAdapter
*/
public void setSortMode(int mode)
{
MediaAdapter adapter = (MediaAdapter)mCurrentAdapter;
SortableAdapter adapter = (SortableAdapter)mCurrentAdapter;
if (mode == adapter.getSortMode())
return;
adapter.setSortMode(mode);
requestRequery(adapter);
requestRequery(mCurrentAdapter);
Handler handler = mWorkerHandler;
handler.sendMessage(handler.obtainMessage(MSG_SAVE_SORT, adapter));

View File

@ -30,7 +30,6 @@ import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.graphics.Color;
import android.net.Uri;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.text.Spannable;
@ -39,10 +38,7 @@ import android.text.style.ForegroundColorSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.SectionIndexer;
import android.widget.TextView;
import java.util.Arrays;
import java.util.List;
@ -62,7 +58,7 @@ import java.lang.StringBuilder;
* See getLimiter and setLimiter for details.
*/
public class MediaAdapter
extends BaseAdapter
extends SortableAdapter
implements LibraryAdapter
, View.OnClickListener
, SectionIndexer
@ -123,21 +119,11 @@ public class MediaAdapter
* The constraint used for filtering, set by the search box.
*/
private String mConstraint;
/**
* The human-readable descriptions for each sort mode.
*/
private int[] mSortEntries;
/**
* An array ORDER BY expressions for each sort mode. %1$s is replaced by
* ASC or DESC as appropriate before being passed to the query.
*/
private String[] mAdapterSortValues;
/**
* The index of the current of the current sort mode in mSortValues, or
* the inverse of the index (in which case sort should be descending
* instead of ascending).
*/
private int mSortMode;
/**
* If true, show the expander button on each row.
*/
@ -309,14 +295,8 @@ public class MediaAdapter
String[] enrichedProjection = projection;
// Assemble the sort string as requested by the user
int mode = mSortMode;
String sortDir;
if (mode < 0) {
mode = ~mode;
sortDir = "DESC";
} else {
sortDir = "ASC";
}
int mode = getSortModeIndex();
String sortDir = isSortDescending() ? "DESC" : "ASC";
// Fetch current sorting mode and sort by disc+track if we are going to look up the songs table
String sortRaw = mAdapterSortValues[mode];
@ -569,33 +549,9 @@ public class MediaAdapter
}
/**
* Return the available sort modes for this adapter.
*
* @return An array containing the resource ids of the sort mode strings.
*/
public int[] getSortEntries()
{
return mSortEntries;
}
/**
* Set the sorting mode. The adapter should be re-queried after changing
* this.
*
* @param i The index of the sort mode in the sort entries array. If this
* is negative, the inverse of the index will be used and sort order will
* be reversed.
*/
public void setSortMode(int mode)
{
int index = ( mode < 0 ? ~mode : mode); // 'negative' modes are actually inverted indexes
mSortMode = index < mSortEntries.length ? mode : 0;
}
/**
* Returns the sort mode that should be used if no preference is saved. This
* may very based on the active limiter.
* {@inheritDoc}
*/
@Override
public int getDefaultSortMode()
{
int type = mType;
@ -605,11 +561,11 @@ public class MediaAdapter
}
/**
* Return the current sort mode set on this adapter.
* {@inheritDoc}
*/
public int getSortMode()
{
return mSortMode;
@Override
public String getSortSettingsKey() {
return String.format("sort_%d_%d", getMediaType(), getLimiterType());
}
/**

View File

@ -0,0 +1,99 @@
/*
* Copyright (C) 2017 Adrian Ulrich <adrian@blinkenlights.ch>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package ch.blinkenlights.android.vanilla;
import android.widget.BaseAdapter;
/**
* Abstract adapter that implements sorting for its data.
*/
public abstract class SortableAdapter extends BaseAdapter {
/**
* The human-readable descriptions for each sort mode.
*/
int[] mSortEntries;
/**
* The index of the current sort mode in mSortValues, or
* the inverse of the index (in which case sort should be descending
* instead of ascending).
*/
int mSortMode;
/**
* Return the available sort modes for this adapter.
*
* @return An array containing the resource ids of the sort mode strings.
*/
public int[] getSortEntries() {
return mSortEntries;
}
/**
* Set the sorting mode. The adapter should be re-queried after changing
* this.
*
* @param mode The index of the sort mode in the sort entries array. If this
* is negative, the inverse of the index will be used and sort order will
* be reversed.
*/
public void setSortMode(int mode) {
mSortMode = getIndexForSortMode(mode) < mSortEntries.length ? mode : 0;
}
/**
* Return the current sort mode set on this adapter.
* This is the index or the bitwise inverse of the index if the sort is descending.
*/
public int getSortMode() {
return mSortMode;
}
/**
* Return the current sort mode index.
*/
public int getSortModeIndex() {
return getIndexForSortMode(mSortMode);
}
/**
* Return if the current sort mode is reversed.
*/
public boolean isSortDescending() {
return mSortMode < 0;
}
/**
* Returns the sort mode that should be used if no preference is saved. This
* may very based on the active limiter.
*/
abstract public int getDefaultSortMode();
/**
* Return the key used for loading/saving the sort mode for this adapter.
*/
abstract public String getSortSettingsKey();
private int getIndexForSortMode(int mode) {
return mode < 0 ? ~mode : mode; // 'negative' modes are actually inverted indexes
}
}