diff --git a/res/values/translatable.xml b/res/values/translatable.xml
index 7432811e..1b09e5f4 100644
--- a/res/values/translatable.xml
+++ b/res/values/translatable.xml
@@ -121,6 +121,11 @@ THE SOFTWARE.
Genre
Folder
+ Filename
+ File size
+ Modification time
+ Extension
+
Unknown
Play all
Enqueue all
diff --git a/src/ch/blinkenlights/android/vanilla/FileSystemAdapter.java b/src/ch/blinkenlights/android/vanilla/FileSystemAdapter.java
index 06efeb60..426b14bc 100644
--- a/src/ch/blinkenlights/android/vanilla/FileSystemAdapter.java
+++ b/src/ch/blinkenlights/android/vanilla/FileSystemAdapter.java
@@ -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 mFileComparator = new Comparator() {
@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!
diff --git a/src/ch/blinkenlights/android/vanilla/FileUtils.java b/src/ch/blinkenlights/android/vanilla/FileUtils.java
index ca9447a6..7e381bcb 100644
--- a/src/ch/blinkenlights/android/vanilla/FileUtils.java
+++ b/src/ch/blinkenlights/android/vanilla/FileUtils.java
@@ -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) : "";
+ }
}
diff --git a/src/ch/blinkenlights/android/vanilla/LibraryActivity.java b/src/ch/blinkenlights/android/vanilla/LibraryActivity.java
index 2af8044a..402ade4a 100644
--- a/src/ch/blinkenlights/android/vanilla/LibraryActivity.java
+++ b/src/ch/blinkenlights/android/vanilla/LibraryActivity.java
@@ -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);
diff --git a/src/ch/blinkenlights/android/vanilla/LibraryPagerAdapter.java b/src/ch/blinkenlights/android/vanilla/LibraryPagerAdapter.java
index cf185055..99e1a632 100644
--- a/src/ch/blinkenlights/android/vanilla/LibraryPagerAdapter.java
+++ b/src/ch/blinkenlights/android/vanilla/LibraryPagerAdapter.java
@@ -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));
diff --git a/src/ch/blinkenlights/android/vanilla/MediaAdapter.java b/src/ch/blinkenlights/android/vanilla/MediaAdapter.java
index c6276524..e29d656d 100644
--- a/src/ch/blinkenlights/android/vanilla/MediaAdapter.java
+++ b/src/ch/blinkenlights/android/vanilla/MediaAdapter.java
@@ -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());
}
/**
diff --git a/src/ch/blinkenlights/android/vanilla/SortableAdapter.java b/src/ch/blinkenlights/android/vanilla/SortableAdapter.java
new file mode 100644
index 00000000..e30eaf4a
--- /dev/null
+++ b/src/ch/blinkenlights/android/vanilla/SortableAdapter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 Adrian Ulrich
+ *
+ * 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
+ }
+}