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:
parent
55a5ff6aa0
commit
10878de963
@ -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>
|
||||
|
@ -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!
|
||||
|
@ -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) : "";
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
99
src/ch/blinkenlights/android/vanilla/SortableAdapter.java
Normal file
99
src/ch/blinkenlights/android/vanilla/SortableAdapter.java
Normal 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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user