Allow choice of sort modes in library
This commit is contained in:
parent
fcad52ef5d
commit
4d43970c27
BIN
res/drawable-hdpi/ic_menu_sort_alphabetically.png
Normal file
BIN
res/drawable-hdpi/ic_menu_sort_alphabetically.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
res/drawable-mdpi/ic_menu_sort_alphabetically.png
Normal file
BIN
res/drawable-mdpi/ic_menu_sort_alphabetically.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
@ -55,6 +55,7 @@ THE SOFTWARE.
|
||||
<string name="expand">Expand</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="playback_view">Now Playing</string>
|
||||
<string name="sort_by">Sort By</string>
|
||||
<string name="search">Search</string>
|
||||
<string name="done">Done</string>
|
||||
|
||||
@ -88,6 +89,15 @@ THE SOFTWARE.
|
||||
<string name="enqueue_all">Enqueue All</string>
|
||||
<string name="all_songs">All Songs</string>
|
||||
|
||||
<string name="name">Name</string>
|
||||
<string name="number_of_tracks">Number of tracks</string>
|
||||
<string name="year">Year</string>
|
||||
<string name="date_added">Date added</string>
|
||||
<string name="artist_album">Artist, album</string>
|
||||
<string name="artist_album_track">Artist, album, track no.</string>
|
||||
<string name="artist_album_title">Artist, album, title</string>
|
||||
<string name="artist_year">Artist, year</string>
|
||||
|
||||
<!-- Notification -->
|
||||
<string name="notification_title_paused">%s (Paused)</string>
|
||||
|
||||
|
@ -361,6 +361,9 @@ public class LibraryActivity
|
||||
}
|
||||
}
|
||||
|
||||
loadSortOrder(mSongAdapter);
|
||||
loadSortOrder(mArtistAdapter);
|
||||
|
||||
requestRequery(mSongAdapter);
|
||||
requestRequery(mAlbumAdapter);
|
||||
|
||||
@ -702,14 +705,31 @@ public class LibraryActivity
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu)
|
||||
{
|
||||
menu.add(0, MENU_PLAYBACK, 0, R.string.playback_view).setIcon(R.drawable.ic_menu_gallery);
|
||||
menu.addSubMenu(0, MENU_SORT, 0, R.string.sort_by).setIcon(R.drawable.ic_menu_sort_alphabetically);
|
||||
menu.add(0, MENU_SEARCH, 0, R.string.search).setIcon(R.drawable.ic_menu_search);
|
||||
menu.add(0, MENU_PLAYBACK, 0, R.string.playback_view).setIcon(R.drawable.ic_menu_gallery);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
public static final int GROUP_SORT = 100;
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item)
|
||||
{
|
||||
if (item.getGroupId() == GROUP_SORT) {
|
||||
MediaAdapter adapter = mCurrentAdapter;
|
||||
ListView view = (ListView)mTabHost.getCurrentView();
|
||||
int i = item.getItemId();
|
||||
adapter.setSortMode(i);
|
||||
requestRequery(adapter);
|
||||
// Force a new FastScroller to be created so the scroll section
|
||||
// are updated.
|
||||
view.setFastScrollEnabled(false);
|
||||
view.setFastScrollEnabled(true);
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_SAVE_SORT, adapter));
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case MENU_SEARCH:
|
||||
setSearchBoxVisible(!mSearchBoxVisible);
|
||||
@ -717,6 +737,9 @@ public class LibraryActivity
|
||||
case MENU_PLAYBACK:
|
||||
startActivity(new Intent(this, FullPlaybackActivity.class));
|
||||
return true;
|
||||
case MENU_SORT:
|
||||
mCurrentAdapter.buildSortMenu(item.getSubMenu());
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@ -744,6 +767,7 @@ public class LibraryActivity
|
||||
|
||||
MediaAdapter adapter = new MediaAdapter(this, type, expandable, hasHeader, limiter);
|
||||
view.setAdapter(adapter);
|
||||
loadSortOrder(adapter);
|
||||
|
||||
Resources res = getResources();
|
||||
String labelRes = res.getString(label);
|
||||
@ -779,6 +803,10 @@ public class LibraryActivity
|
||||
* Call addToPlaylist with data from the intent in obj.
|
||||
*/
|
||||
private static final int MSG_ADD_TO_PLAYLIST = 15;
|
||||
/**
|
||||
* Save the sort mode for the adapter passed in obj.
|
||||
*/
|
||||
private static final int MSG_SAVE_SORT = 16;
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message)
|
||||
@ -824,6 +852,13 @@ public class LibraryActivity
|
||||
});
|
||||
break;
|
||||
}
|
||||
case MSG_SAVE_SORT: {
|
||||
MediaAdapter adapter = (MediaAdapter)message.obj;
|
||||
SharedPreferences.Editor editor = PlaybackService.getSettings(this).edit();
|
||||
editor.putInt(String.format("sort_%d_%d", adapter.getMediaType(), adapter.getLimiterType()), adapter.getSortMode());
|
||||
editor.commit();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return super.handleMessage(message);
|
||||
}
|
||||
@ -928,4 +963,18 @@ public class LibraryActivity
|
||||
runQuery(adapter);
|
||||
updateLimiterViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the saved sort mode for the given adapter. The adapter should
|
||||
* be re-queried after calling this.
|
||||
*
|
||||
* @param adapter The adapter to load for.
|
||||
*/
|
||||
private void loadSortOrder(MediaAdapter adapter)
|
||||
{
|
||||
String key = String.format("sort_%d_%d", adapter.getMediaType(), adapter.getLimiterType());
|
||||
int def = adapter.getDefaultSortMode();
|
||||
int sort = PlaybackService.getSettings(this).getInt(key, def);
|
||||
adapter.setSortMode(sort);
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import android.database.DatabaseUtils;
|
||||
import android.net.Uri;
|
||||
import android.provider.BaseColumns;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CursorAdapter;
|
||||
@ -113,6 +114,9 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
* True if the data is stale and the query should be re-run.
|
||||
*/
|
||||
private boolean mNeedsRequery;
|
||||
private int[] mSortEntries;
|
||||
private String[] mSortValues;
|
||||
private int mSortMode;
|
||||
|
||||
/**
|
||||
* Construct a MediaAdapter representing the given <code>type</code> of
|
||||
@ -124,7 +128,7 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
* and what fields to display in the views.
|
||||
* @param expandable Whether an expand arrow should be shown to the right
|
||||
* of the views' text
|
||||
* @param hasHeader Wether this view has a header row.
|
||||
* @param hasHeader Whether this view has a header row.
|
||||
* @param limiter An initial limiter to use
|
||||
*/
|
||||
public MediaAdapter(Context context, int type, boolean expandable, boolean hasHeader, Limiter limiter)
|
||||
@ -145,6 +149,8 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
mFields = new String[] { MediaStore.Audio.Artists.ARTIST };
|
||||
mFieldKeys = new String[] { MediaStore.Audio.Artists.ARTIST_KEY };
|
||||
mSongSort = MediaUtils.DEFAULT_SORT;
|
||||
mSortEntries = new int[] { R.string.name, R.string.number_of_tracks };
|
||||
mSortValues = new String[] { "artist_key", "number_of_tracks" };
|
||||
break;
|
||||
case MediaUtils.TYPE_ALBUM:
|
||||
mStore = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
|
||||
@ -152,21 +158,29 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
// Why is there no artist_key column constant in the album MediaStore? The column does seem to exist.
|
||||
mFieldKeys = new String[] { "artist_key", MediaStore.Audio.Albums.ALBUM_KEY };
|
||||
mSongSort = "album_key,track";
|
||||
mSortEntries = new int[] { R.string.name, R.string.artist_album, R.string.year, R.string.number_of_tracks };
|
||||
mSortValues = new String[] { "album_key", "artist_key,album_key", "minyear", "numsongs" };
|
||||
break;
|
||||
case MediaUtils.TYPE_SONG:
|
||||
mStore = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
mFields = new String[] { MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TITLE };
|
||||
mFieldKeys = new String[] { MediaStore.Audio.Media.ARTIST_KEY, MediaStore.Audio.Media.ALBUM_KEY, MediaStore.Audio.Media.TITLE_KEY };
|
||||
mSortEntries = new int[] { R.string.name, R.string.artist_album_track, R.string.artist_album_title, R.string.artist_year, R.string.year };
|
||||
mSortValues = new String[] { "title_key", "artist_key,album_key,track", "artist_key,album_key,title_key", "artist_key,year", "year" };
|
||||
break;
|
||||
case MediaUtils.TYPE_PLAYLIST:
|
||||
mStore = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
|
||||
mFields = new String[] { MediaStore.Audio.Playlists.NAME };
|
||||
mFieldKeys = null;
|
||||
mSortEntries = new int[] { R.string.name, R.string.date_added };
|
||||
mSortValues = new String[] { "name", "date_added" };
|
||||
break;
|
||||
case MediaUtils.TYPE_GENRE:
|
||||
mStore = MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI;
|
||||
mFields = new String[] { MediaStore.Audio.Genres.NAME };
|
||||
mFieldKeys = null;
|
||||
mSortEntries = new int[] { R.string.name };
|
||||
mSortValues = new String[] { "name" };
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid value for type: " + type);
|
||||
@ -292,13 +306,7 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
StringBuilder selection = new StringBuilder();
|
||||
String[] selectionArgs = null;
|
||||
|
||||
String sort;
|
||||
if (limiter != null && limiter.type == MediaUtils.TYPE_ALBUM)
|
||||
sort = MediaStore.Audio.Media.TRACK;
|
||||
else if (mFieldKeys == null)
|
||||
sort = mFields[mFields.length - 1];
|
||||
else
|
||||
sort = mFieldKeys[mFieldKeys.length - 1];
|
||||
String sort = mSortValues[mSortMode];
|
||||
|
||||
if (mType == MediaUtils.TYPE_SONG || forceMusicCheck)
|
||||
selection.append("is_music!=0");
|
||||
@ -346,7 +354,7 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
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. : /
|
||||
return MediaUtils.buildGenreQuery(limiter.id, projection, selection.toString(), selectionArgs);
|
||||
return MediaUtils.buildGenreQuery(limiter.id, projection, selection.toString(), selectionArgs, sort);
|
||||
} else {
|
||||
if (limiter != null) {
|
||||
if (selection.length() != 0)
|
||||
@ -379,6 +387,8 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
QueryTask query = buildQuery(projection, true);
|
||||
if (mType != MediaUtils.TYPE_SONG) {
|
||||
query.setUri(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
|
||||
// Would be better to match the sort order in the adapter. This
|
||||
// is likely to require significantly more work though.
|
||||
query.setSortOrder(mSongSort);
|
||||
}
|
||||
return query;
|
||||
@ -471,6 +481,8 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
@Override
|
||||
public Object[] getSections()
|
||||
{
|
||||
if (mSortMode != 0)
|
||||
return null;
|
||||
return mIndexer.getSections();
|
||||
}
|
||||
|
||||
@ -511,6 +523,64 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
|
||||
return new MediaView(mContext, null, mExpandable ? MediaView.sExpander : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the current limiter.
|
||||
*
|
||||
* @return One of MediaUtils.TYPE_, or MediaUtils.TYPE_INVALID if there is
|
||||
* no limiter set.
|
||||
*/
|
||||
public int getLimiterType()
|
||||
{
|
||||
Limiter limiter = mLimiter;
|
||||
if (limiter != null)
|
||||
return limiter.type;
|
||||
return MediaUtils.TYPE_INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a menu of sort modes appropriate for this adapter.
|
||||
*
|
||||
* @param menu The menu the items will be added to.
|
||||
*/
|
||||
public void buildSortMenu(SubMenu menu)
|
||||
{
|
||||
int[] entries = mSortEntries;
|
||||
menu.clear();
|
||||
for (int i = 0; i != entries.length; ++i)
|
||||
menu.add(LibraryActivity.GROUP_SORT, i, 0, entries[i]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sorting mode. The adapter should be re-queried after changing
|
||||
* this.
|
||||
*
|
||||
* @param i The index of the sort mode.
|
||||
*/
|
||||
public void setSortMode(int i)
|
||||
{
|
||||
mSortMode = i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the sort mode that should be used if no preference is saved. This
|
||||
* may very based on the active limiter.
|
||||
*/
|
||||
public int getDefaultSortMode()
|
||||
{
|
||||
Limiter limiter = mLimiter;
|
||||
if (limiter != null && limiter.type == MediaUtils.TYPE_SONG)
|
||||
return 1; // artist,album,track
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current sort mode set on this adapter.
|
||||
*/
|
||||
public int getSortMode()
|
||||
{
|
||||
return mSortMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limiter is a constraint for MediaAdapters used when a row is "expanded".
|
||||
*/
|
||||
|
@ -33,6 +33,10 @@ import java.util.Random;
|
||||
import junit.framework.Assert;
|
||||
|
||||
public class MediaUtils {
|
||||
/**
|
||||
* A special invalid media type.
|
||||
*/
|
||||
public static final int TYPE_INVALID = 0;
|
||||
/**
|
||||
* Type indicating an id represents an artist.
|
||||
*/
|
||||
@ -159,11 +163,11 @@ public class MediaUtils {
|
||||
* @param projection The columns to query.
|
||||
* @param selection The selection to pass to the query, or null.
|
||||
* @param selectionArgs The arguments to substitute into the selection.
|
||||
* @param sort The sort order.
|
||||
*/
|
||||
public static QueryTask buildGenreQuery(long id, String[] projection, String selection, String[] selectionArgs)
|
||||
public static QueryTask buildGenreQuery(long id, String[] projection, String selection, String[] selectionArgs, String sort)
|
||||
{
|
||||
Uri uri = MediaStore.Audio.Genres.Members.getContentUri("external", id);
|
||||
String sort = MediaStore.Audio.Genres.Members.TITLE_KEY;
|
||||
return new QueryTask(uri, projection, selection, selectionArgs, sort);
|
||||
}
|
||||
|
||||
@ -187,7 +191,7 @@ public class MediaUtils {
|
||||
case TYPE_PLAYLIST:
|
||||
return buildPlaylistQuery(id, projection, selection);
|
||||
case TYPE_GENRE:
|
||||
return buildGenreQuery(id, projection, selection, null);
|
||||
return buildGenreQuery(id, projection, selection, null, MediaStore.Audio.Genres.Members.TITLE_KEY);
|
||||
default:
|
||||
throw new IllegalArgumentException("Specified type not valid: " + type);
|
||||
}
|
||||
|
@ -355,6 +355,7 @@ public class PlaybackActivity extends Activity
|
||||
{
|
||||
}
|
||||
|
||||
static final int MENU_SORT = 1;
|
||||
static final int MENU_PREFS = 2;
|
||||
static final int MENU_LIBRARY = 3;
|
||||
static final int MENU_PLAYBACK = 5;
|
||||
|
Loading…
x
Reference in New Issue
Block a user