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
<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>
<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>
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;
/**
* The result
*/
- private final ArrayList<Album> mAlbumsList = Lists.newArrayList();
+ private ArrayList<Album> mAlbumsList = Lists.newArrayList();
/**
* The {@link Cursor} used to run the query.
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.
*
* @param context The {@link Context} to use.
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;
/**
* The result
*/
- private final ArrayList<Artist> mArtistsList = Lists.newArrayList();
+ private ArrayList<Artist> mArtistsList = Lists.newArrayList();
/**
* The {@link Cursor} used to run the query.
// Create a new artist
final Artist artist = new Artist(id, artistName, songCount, albumCount);
- // Add everything up
mArtistsList.add(artist);
} while (mCursor.moveToNext());
}
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.
*
* @param context The {@link Context} to use.
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;
/**
* The result
*/
- protected final ArrayList<Song> mSongList = Lists.newArrayList();
+ protected ArrayList<Song> mSongList = Lists.newArrayList();
/**
* The {@link Cursor} used to run the query.
public List<Song> loadInBackground() {
// Create the Cursor
mCursor = getCursor();
+
// Gather the data
if (mCursor != null && mCursor.moveToFirst()) {
do {
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());
}
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
*/
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;
*/
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) {
// 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;
}
}
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");
+ }
}
package com.cyngn.eleven.utils;
import android.content.Context;
+import android.text.TextUtils;
import com.cyngn.eleven.Config;
import com.cyngn.eleven.R;
public String createFooterLabel(T item) {
return null;
}
+
+ // partial sectioning helper functions
+
+ public boolean shouldStopSectionCreation() {
+ return false;
+ }
}
/**
* @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;
}
@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;
}
/**
}
public abstract String getString(T item);
+
+ @Override
+ public boolean shouldStopSectionCreation() {
+ return mStopSectionCreation;
+ }
}
/**
}
/**
- * 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
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;
+ }
}
}
}
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;
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;
// 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;
--- /dev/null
+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;
+ }
+
+}