Commit 493aa1d6 authored by Rohit Yengisetty's avatar Rohit Yengisetty Committed by linus_lee
Browse files

Eleven - Introducing the ability to filter a list items into two - localizable

and non-localizable based on a specified attribute of the item.

Udpating the SongFragment  and ArtistFragment to list localizable songs
towards the top, while pushing the others towards the bottom of the list.
Also, updated the section creator logic to *not* create sections for the non-
latin named songs.

https://cyanogen.atlassian.net/browse/MUSIC-73

Change-Id: I5aaa98ae4312a9f1d142a48aaf025bdbc7e0150b

Conflicts:
	src/com/cyngn/eleven/loaders/AlbumLoader.java
	src/com/cyngn/eleven/loaders/ArtistLoader.java
	src/com/cyngn/eleven/loaders/SongLoader.java
parent 20a4fe3d
......@@ -183,6 +183,8 @@
<string name="header_5_plus_albums">5+ albums</string>
<string name="header_other">"Other"</string>
<string name="footer_search_artists">Show all artists</string>
<string name="footer_search_albums">Show all albums</string>
<string name="footer_search_songs">Show all songs</string>
......@@ -196,4 +198,4 @@
<string name="duration_format"><xliff:g id="hours">%1$s</xliff:g> <xliff:g id="minutes">%2$s</xliff:g></string>
</resources>
\ No newline at end of file
</resources>
......@@ -17,14 +17,12 @@ import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AlbumColumns;
import com.cyngn.eleven.R;
import com.cyngn.eleven.model.Album;
import com.cyngn.eleven.sectionadapter.SectionCreator;
import com.cyngn.eleven.utils.Lists;
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.utils.PreferenceUtils;
import com.cyngn.eleven.utils.SectionCreatorUtils;
import com.cyngn.eleven.utils.SortOrder;
import com.cyngn.eleven.utils.SortUtils;
import java.util.ArrayList;
import java.util.List;
......@@ -40,7 +38,7 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
/**
* The result
*/
private final ArrayList<Album> mAlbumsList = Lists.newArrayList();
private ArrayList<Album> mAlbumsList = Lists.newArrayList();
/**
* The {@link Cursor} used to run the query.
......@@ -99,9 +97,29 @@ public class AlbumLoader extends SectionCreator.SimpleListLoader<Album> {
mCursor = null;
}
// requested album ordering
String albumSortOrder = PreferenceUtils.getInstance(mContext).getAlbumSortOrder();
// run a custom localized sort to try to fit items in to header buckets more nicely
if (shouldEvokeCustomSortRoutine(albumSortOrder)) {
mAlbumsList = SortUtils.localizeSortList(mAlbumsList, albumSortOrder);
}
return mAlbumsList;
}
/**
* Evoke custom sorting routine if the sorting attribute is a String. MediaProvider's sort
* can be trusted in other instances
* @param sortOrder
* @return
*/
private boolean shouldEvokeCustomSortRoutine(String sortOrder) {
return sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z) ||
sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A) ||
sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST);
}
/**
* Creates the {@link Cursor} used to run the query.
*
......
......@@ -17,11 +17,12 @@ import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.ArtistColumns;
import com.cyngn.eleven.R;
import com.cyngn.eleven.model.Artist;
import com.cyngn.eleven.sectionadapter.SectionCreator;
import com.cyngn.eleven.utils.Lists;
import com.cyngn.eleven.utils.PreferenceUtils;
import com.cyngn.eleven.utils.SortOrder;
import com.cyngn.eleven.utils.SortUtils;
import java.util.ArrayList;
import java.util.List;
......@@ -37,7 +38,7 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
/**
* The result
*/
private final ArrayList<Artist> mArtistsList = Lists.newArrayList();
private ArrayList<Artist> mArtistsList = Lists.newArrayList();
/**
* The {@link Cursor} used to run the query.
......@@ -83,7 +84,6 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
// Create a new artist
final Artist artist = new Artist(id, artistName, songCount, albumCount);
// Add everything up
mArtistsList.add(artist);
} while (mCursor.moveToNext());
}
......@@ -92,9 +92,28 @@ public class ArtistLoader extends SectionCreator.SimpleListLoader<Artist> {
mCursor.close();
mCursor = null;
}
// requested artist ordering
String artistSortOrder = PreferenceUtils.getInstance(mContext).getArtistSortOrder();
// run a custom localized sort to try to fit items in to header buckets more nicely
if (shouldEvokeCustomSortRoutine(artistSortOrder)) {
mArtistsList = SortUtils.localizeSortList(mArtistsList, artistSortOrder);
}
return mArtistsList;
}
/**
* Evoke custom sorting routine if the sorting attribute is a String. MediaProvider's sort
* can be trusted in other instances
* @param sortOrder
* @return
*/
private boolean shouldEvokeCustomSortRoutine(String sortOrder) {
return sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z) ||
sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A);
}
/**
* Creates the {@link Cursor} used to run the query.
*
......
......@@ -22,6 +22,8 @@ import com.cyngn.eleven.sectionadapter.SectionCreator;
import com.cyngn.eleven.utils.Lists;
import com.cyngn.eleven.utils.MusicUtils;
import com.cyngn.eleven.utils.PreferenceUtils;
import com.cyngn.eleven.utils.SortOrder;
import com.cyngn.eleven.utils.SortUtils;
import java.util.ArrayList;
import java.util.List;
......@@ -37,7 +39,7 @@ public class SongLoader extends SectionCreator.SimpleListLoader<Song> {
/**
* The result
*/
protected final ArrayList<Song> mSongList = Lists.newArrayList();
protected ArrayList<Song> mSongList = Lists.newArrayList();
/**
* The {@link Cursor} used to run the query.
......@@ -60,6 +62,7 @@ public class SongLoader extends SectionCreator.SimpleListLoader<Song> {
public List<Song> loadInBackground() {
// Create the Cursor
mCursor = getCursor();
// Gather the data
if (mCursor != null && mCursor.moveToFirst()) {
do {
......@@ -88,9 +91,9 @@ public class SongLoader extends SectionCreator.SimpleListLoader<Song> {
final int year = mCursor.getInt(6);
// Create a new song
final Song song = new Song(id, songName, artist, album, albumId, durationInSecs, year);
final Song song = new Song(id, songName, artist, album, albumId,
durationInSecs, year);
// Add everything up
mSongList.add(song);
} while (mCursor.moveToNext());
}
......@@ -99,9 +102,31 @@ public class SongLoader extends SectionCreator.SimpleListLoader<Song> {
mCursor.close();
mCursor = null;
}
// requested ordering of songs
String songSortOrder = PreferenceUtils.getInstance(mContext).getSongSortOrder();
// run a custom localized sort to try to fit items in to header buckets more nicely
if (shouldEvokeCustomSortRoutine(songSortOrder)) {
mSongList = SortUtils.localizeSortList(mSongList, songSortOrder);
}
return mSongList;
}
/**
* We are choosing to custom sort the song list for a cleaner look on the UI side for a few
* sort options
* @param sortOrder the song ordering preference selected by the user
* @return
*/
private boolean shouldEvokeCustomSortRoutine(String sortOrder) {
return sortOrder.equals(SortOrder.SongSortOrder.SONG_A_Z) ||
sortOrder.equals(SortOrder.SongSortOrder.SONG_Z_A) ||
sortOrder.equals(SortOrder.SongSortOrder.SONG_ALBUM) ||
sortOrder.equals(SortOrder.SongSortOrder.SONG_ARTIST);
}
/**
* Gets the cursor for the loader - can be overriden
* @return cursor to load
......
......@@ -49,7 +49,10 @@ import com.cyngn.eleven.loaders.PlaylistSongLoader;
import com.cyngn.eleven.loaders.SongLoader;
import com.cyngn.eleven.loaders.TopTracksLoader;
import com.cyngn.eleven.menu.FragmentMenuItems;
import com.cyngn.eleven.model.Album;
import com.cyngn.eleven.model.AlbumArtistDetails;
import com.cyngn.eleven.model.Artist;
import com.cyngn.eleven.model.Song;
import com.cyngn.eleven.provider.RecentStore;
import com.cyngn.eleven.provider.SongPlayCount;
import com.cyngn.eleven.service.MusicPlaybackTrack;
......@@ -896,10 +899,10 @@ public final class MusicUtils {
*/
public static final long getIdForPlaylist(final Context context, final String name) {
Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, new String[] {
BaseColumns._ID
}, PlaylistsColumns.NAME + "=?", new String[] {
name
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, new String[]{
BaseColumns._ID
}, PlaylistsColumns.NAME + "=?", new String[]{
name
}, PlaylistsColumns.NAME);
int id = -1;
if (cursor != null) {
......@@ -1688,7 +1691,7 @@ public final class MusicUtils {
// not quite sorted
if (lbl != null && lbl.length() > 0) {
char ch = lbl.charAt(0);
if (ch < 'A' && ch > 'Z' && ch != '#') {
if ((ch < 'A' || ch > 'Z') && ch != '#') {
return null;
}
}
......@@ -1730,4 +1733,55 @@ public final class MusicUtils {
removeFromCache(activity, key);
MusicUtils.refresh();
}
/**
* Determines the correct item attribute to use for a given sort request and generates the
* localized bucket for that attribute
* @param item
* @param sortOrder
* @param <T>
* @return
*/
public static <T> String getLocalizedBucketLetterByAttribute(T item, String sortOrder) {
if (item instanceof Song) {
// we aren't 'trimming' certain attributes - a flag for such attributes
boolean trimName = true;
String attributeToLocalize = ((Song)item).mSongName;
// select Song attribute based on the sort order
if (sortOrder.equals(SortOrder.SongSortOrder.SONG_ARTIST) ) {
attributeToLocalize = ((Song)item).mArtistName;
trimName = false;
} else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_ALBUM) ) {
attributeToLocalize = ((Song)item).mAlbumName;
}
return getLocalizedBucketLetter(attributeToLocalize, trimName);
} else if (item instanceof Artist) {
return getLocalizedBucketLetter(((Artist)item).mArtistName, true);
} else if (item instanceof Album) {
// we aren't 'trimming' certain attributes - a flag for such attributes
boolean trimName = true;
String attributeToLocalize = ((Album)item).mAlbumName;
if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST) ) {
attributeToLocalize = ((Album)item).mArtistName;
trimName = false;
}
return getLocalizedBucketLetter(attributeToLocalize, trimName);
}
return null;
}
/**
*
* @param sortOrder values are mostly derived from SortOrder.class or could also be any sql
* order clause
* @return
*/
public static boolean isSortOrderDesending(String sortOrder) {
return sortOrder.endsWith(" DESC");
}
}
......@@ -4,6 +4,7 @@
package com.cyngn.eleven.utils;
import android.content.Context;
import android.text.TextUtils;
import com.cyngn.eleven.Config;
import com.cyngn.eleven.R;
......@@ -93,6 +94,12 @@ public class SectionCreatorUtils {
public String createFooterLabel(T item) {
return null;
}
// partial sectioning helper functions
public boolean shouldStopSectionCreation() {
return false;
}
}
/**
......@@ -100,11 +107,21 @@ public class SectionCreatorUtils {
* @param <T> the type of item to compare
*/
public static abstract class LocalizedCompare<T> extends IItemCompare<T> {
protected Context mContext;
private boolean mStopSectionCreation;
public LocalizedCompare(Context context) {
mContext = context;
mStopSectionCreation = false;
}
@Override
public String createSectionHeader(T first, T second) {
String secondLabel = createHeaderLabel(second);
// if we can't determine a good label then don't bother creating a section
if (secondLabel == null) {
// stop section creation as the items further down the list
mStopSectionCreation = true;
return null;
}
......@@ -117,7 +134,11 @@ public class SectionCreatorUtils {
@Override
public String createHeaderLabel(T item) {
return MusicUtils.getLocalizedBucketLetter(getString(item), trimName());
final String label = MusicUtils.getLocalizedBucketLetter(getString(item), trimName());
if (TextUtils.isEmpty(label)) {
return mContext.getString(R.string.header_other);
}
return label;
}
/**
......@@ -129,6 +150,11 @@ public class SectionCreatorUtils {
}
public abstract String getString(T item);
@Override
public boolean shouldStopSectionCreation() {
return mStopSectionCreation;
}
}
/**
......@@ -301,7 +327,7 @@ public class SectionCreatorUtils {
}
/**
* This creates the sections give a list of items and the comparison algorithm
* This creates the sections given a list of items and the comparison algorithm
* @param list The list of items to analyze
* @param comparator The comparison function to use
* @param <T> the type of item to compare
......@@ -330,6 +356,10 @@ public class SectionCreatorUtils {
if (header != null) {
// add sectionHeaders.size() to store the indices of the combined list
sections.put(sections.size() + i, new Section(SectionType.Header, header));
// stop section creation
if (comparator.shouldStopSectionCreation()) {
break;
}
}
}
}
......@@ -351,7 +381,7 @@ public class SectionCreatorUtils {
String sortOrder = PreferenceUtils.getInstance(context).getArtistSortOrder();
if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z)
|| sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A)) {
sectionCreator = new SectionCreatorUtils.LocalizedCompare<Artist>() {
sectionCreator = new SectionCreatorUtils.LocalizedCompare<Artist>(context) {
@Override
public String getString(Artist item) {
return item.mArtistName;
......@@ -387,14 +417,14 @@ public class SectionCreatorUtils {
String sortOrder = PreferenceUtils.getInstance(context).getAlbumSortOrder();
if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z)
|| sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A)) {
sectionCreator = new LocalizedCompare<Album>() {
sectionCreator = new LocalizedCompare<Album>(context) {
@Override
public String getString(Album item) {
return item.mAlbumName;
}
};
} else if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST)) {
sectionCreator = new LocalizedCompare<Album>() {
sectionCreator = new LocalizedCompare<Album>(context) {
@Override
public String getString(Album item) {
return item.mArtistName;
......@@ -461,21 +491,21 @@ public class SectionCreatorUtils {
// so we will not return a sectionCreator for that one
if (sortOrder.equals(SortOrder.SongSortOrder.SONG_A_Z)
|| sortOrder.equals(SortOrder.SongSortOrder.SONG_Z_A)) {
sectionCreator = new LocalizedCompare<Song>() {
sectionCreator = new LocalizedCompare<Song>(context) {
@Override
public String getString(Song item) {
return item.mSongName;
}
};
} else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_ALBUM)) {
sectionCreator = new LocalizedCompare<Song>() {
sectionCreator = new LocalizedCompare<Song>(context) {
@Override
public String getString(Song item) {
return item.mAlbumName;
}
};
} else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_ARTIST)) {
sectionCreator = new LocalizedCompare<Song>() {
sectionCreator = new LocalizedCompare<Song>(context) {
@Override
public String getString(Song item) {
return item.mArtistName;
......
package com.cyngn.eleven.utils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeMap;
/**
* Implementation of custom sorting routines of song list
*/
public class SortUtils {
/**
* Sorts items based on the localized bucket letter they belong to and the sort order specified
* @param items the original list of items
* @param sortOrder values derived from SortOrder.class
* @return the new sorted list
*/
public static <T> ArrayList<T> localizeSortList(ArrayList<T> items, String sortOrder) {
ArrayList<T> finalList = Lists.newArrayList();
// map of items grouped by their localized label
TreeMap<String, LinkedList<T>> mappedList = new TreeMap<String, LinkedList<T>>();
// list holding items that don't have a localized label
ArrayList<T> nonLocalizableItems = Lists.newArrayList();
for (T item : items) {
// get the bucket letter based on the attribute to sort by
String label = MusicUtils.getLocalizedBucketLetterByAttribute(item, sortOrder);
//divvy items based on their localized bucket letter
if (label != null) {
if (mappedList.get(label) == null) {
// create new label slot to assign items
mappedList.put(label, Lists.<T>newLinkedList());
}
// add item to the label's list
mappedList.get(label).add(item);
} else {
nonLocalizableItems.add(item);
}
}
// generate a sorted item list out of localizable items
boolean isDescendingSort = MusicUtils.isSortOrderDesending(sortOrder);
finalList.addAll(getSortedList(mappedList, isDescendingSort));
finalList.addAll(nonLocalizableItems);
return finalList;
}
/**
* Traverses a tree map of a divvied up list to generate a sorted list
* @param mappedList the bucketized list of items based on the header
* @param reverseOrder dictates the order in which the TreeMap is traversed (descending order
* if true)
* @return the combined sorted list
*/
private static <T> ArrayList<T> getSortedList(TreeMap<String, LinkedList<T>> mappedList,
boolean reverseOrder) {
ArrayList<T> sortedList = Lists.newArrayList();
Iterator<String> iterator = mappedList.navigableKeySet().iterator();
if (reverseOrder) {
iterator = mappedList.navigableKeySet().descendingIterator();
}
while (iterator.hasNext()) {
LinkedList<T> list = mappedList.get(iterator.next());
sortedList.addAll(list);
}
return sortedList;
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment