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 + } +}