OSDN Git Service

Eleven - Introducing the ability to filter a list items into two - localizable
authorRohit Yengisetty <rohit@cyngn.com>
Wed, 22 Oct 2014 23:32:26 +0000 (16:32 -0700)
committerlinus_lee <llee@cyngn.com>
Wed, 26 Nov 2014 00:43:10 +0000 (16:43 -0800)
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

res/values/strings.xml
src/com/cyngn/eleven/loaders/AlbumLoader.java
src/com/cyngn/eleven/loaders/ArtistLoader.java
src/com/cyngn/eleven/loaders/SongLoader.java
src/com/cyngn/eleven/utils/MusicUtils.java
src/com/cyngn/eleven/utils/SectionCreatorUtils.java
src/com/cyngn/eleven/utils/SortUtils.java [new file with mode: 0644]

index 8f88618..c6acc4f 100644 (file)
 
     <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>
index f3348b8..4be993a 100644 (file)
@@ -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,10 +97,30 @@ 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.
      * 
      * @param context The {@link Context} to use.
index 87767bc..c5928f7 100644 (file)
@@ -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,10 +92,29 @@ 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.
      * 
      * @param context The {@link Context} to use.
index 86190f0..717804d 100644 (file)
@@ -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,10 +102,32 @@ 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
      */
index 7b71e3f..e100e87 100644 (file)
@@ -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");
+    }
 }
index 3e45888..8c8bac3 100644 (file)
@@ -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;
diff --git a/src/com/cyngn/eleven/utils/SortUtils.java b/src/com/cyngn/eleven/utils/SortUtils.java
new file mode 100644 (file)
index 0000000..07cab1e
--- /dev/null
@@ -0,0 +1,75 @@
+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;
+    }
+
+}