OSDN Git Service

9d563d219fb8657bcd43bacdf4f297676140ed2f
[android-x86/packages-apps-Eleven.git] / src / com / cyngn / eleven / utils / SectionCreatorUtils.java
1 /*
2  * Copyright (C) 2014 Cyanogen, Inc.
3  */
4 package com.cyngn.eleven.utils;
5
6 import android.content.Context;
7
8 import com.cyngn.eleven.R;
9 import com.cyngn.eleven.model.Album;
10 import com.cyngn.eleven.model.Artist;
11 import com.cyngn.eleven.model.Song;
12
13 import java.util.List;
14 import java.util.TreeMap;
15
16 /**
17  * This Utils class contains code that compares two different items and determines whether
18  * a section should be created
19  */
20 public class SectionCreatorUtils {
21     /**
22      * Interface to compare two items and create labels
23      * @param <T> type of item to compare
24      */
25     public static interface IItemCompare<T> {
26         /**
27          * Compares to items and returns a section divider T if there should
28          * be a section divider between first and second
29          * @param first the first element in the list.  If null, it is checking to see
30          *              if we need a divider at the beginning of the list
31          * @param second the second element in the list.
32          * @return String the expected separator label or null if none
33          */
34         public String createSectionSeparator(T first, T second);
35
36         /**
37          * Returns the section label that corresponds to this item
38          * @param item the item
39          * @return the section label that this label falls under
40          */
41         public String createLabel(T item);
42     }
43
44
45     /**
46      * A localized String comparison implementation of IItemCompare
47      * @param <T> the type of item to compare
48      */
49     public static abstract class LocalizedCompare<T> implements IItemCompare<T> {
50         @Override
51         public String createSectionSeparator(T first, T second) {
52             String secondLabel = createLabel(second);
53             // if we can't determine a good label then don't bother creating a section
54             if (secondLabel == null) {
55                 return null;
56             }
57
58             if (first == null || !secondLabel.equals(createLabel(first))) {
59                 return secondLabel;
60             }
61
62             return null;
63         }
64
65         @Override
66         public String createLabel(T item) {
67             return MusicUtils.getLocalizedBucketLetter(getString(item), trimName());
68         }
69
70         /**
71          * @return true if we want to trim the name first - apparently artists don't trim
72          * but albums/songs do
73          */
74         public boolean trimName() {
75             return true;
76         }
77
78         public abstract String getString(T item);
79     }
80
81     /**
82      * A simple int comparison implementation of IItemCompare
83      * @param <T> the type of item to compare
84      */
85     public static abstract class IntCompare<T> implements IItemCompare<T> {
86         @Override
87         public String createSectionSeparator(T first, T second) {
88             if (first == null || getInt(first) != getInt(second)) {
89                 return createLabel(second);
90             }
91
92             return null;
93         }
94
95         @Override
96         public String createLabel(T item) {
97             return String.valueOf(getInt(item));
98         }
99
100         public abstract int getInt(T item);
101     }
102
103     /**
104      * A Bounded int comparison implementation of IntCompare
105      * Basically this will take ints and determine what bounds it falls into
106      * For example, 1-5 mintes, 5-10 minutes, 10+ minutes
107      * @param <T> the type of item to compare
108      */
109     public static abstract class BoundedIntCompare<T> extends IntCompare<T> {
110         protected Context mContext;
111
112         public BoundedIntCompare(Context context) {
113             mContext = context;
114         }
115
116         protected abstract int getStringId(int value);
117
118         @Override
119         public String createSectionSeparator(T first, T second) {
120             int secondStringId = getStringId(getInt(second));
121             if (first == null || getStringId(getInt(first)) != secondStringId) {
122                 return createLabel(secondStringId, second);
123             }
124
125             return null;
126         }
127
128         protected String createLabel(int stringId, T item) {
129             return mContext.getString(stringId);
130         }
131
132         @Override
133         public String createLabel(T item) {
134             return createLabel(getStringId(getInt(item)), item);
135         }
136     }
137
138     /**
139      * This implements BoundedIntCompare and gives duration buckets
140      * @param <T> the type of item to compare
141      */
142     public static abstract class DurationCompare<T> extends BoundedIntCompare<T> {
143         private static final int SECONDS_PER_MINUTE = 60;
144
145         public DurationCompare(Context context) {
146             super(context);
147         }
148
149         @Override
150         protected int getStringId(int value) {
151             if (value < 30) {
152                 return R.string.header_less_than_30s;
153             } else if (value < 1 * SECONDS_PER_MINUTE) {
154                 return R.string.header_30_to_60_seconds;
155             } else if (value < 2 * SECONDS_PER_MINUTE) {
156                 return R.string.header_1_to_2_minutes;
157             } else if (value < 3 * SECONDS_PER_MINUTE) {
158                 return R.string.header_2_to_3_minutes;
159             } else if (value < 4 * SECONDS_PER_MINUTE) {
160                 return R.string.header_3_to_4_minutes;
161             } else if (value < 5 * SECONDS_PER_MINUTE) {
162                 return R.string.header_4_to_5_minutes;
163             } else if (value < 10 * SECONDS_PER_MINUTE) {
164                 return R.string.header_5_to_10_minutes;
165             } else if (value < 30 * SECONDS_PER_MINUTE) {
166                 return R.string.header_10_to_30_minutes;
167             } else if (value < 60 * SECONDS_PER_MINUTE) {
168                 return R.string.header_30_to_60_minutes;
169             }
170
171             return R.string.header_greater_than_60_minutes;
172         }
173     }
174
175     /**
176      * This implements BoundedIntCompare and gives number of songs buckets
177      * @param <T> the type of item to compare
178      */
179     public static abstract class NumberOfSongsCompare<T> extends BoundedIntCompare<T> {
180         public NumberOfSongsCompare(Context context) {
181             super(context);
182         }
183
184         @Override
185         protected int getStringId(int value) {
186             if (value <= 1) {
187                 return R.string.header_1_song;
188             } else if (value <= 4) {
189                 return R.string.header_2_to_4_songs;
190             } else if (value <= 9) {
191                 return R.string.header_5_to_9_songs;
192             }
193
194             return R.string.header_10_plus_songs;
195         }
196     }
197
198     /**
199      * This implements BoundedIntCompare and gives number of albums buckets
200      * @param <T> the type of item to compare
201      */
202     public static abstract class NumberOfAlbumsCompare<T> extends BoundedIntCompare<T> {
203         public NumberOfAlbumsCompare(Context context) {
204             super(context);
205         }
206
207         @Override
208         protected int getStringId(int value) {
209             if (value <= 1) {
210                 return R.string.header_1_album;
211             } else if (value <= 4) {
212                 return R.string.header_n_albums;
213             }
214
215             return R.string.header_5_plus_albums;
216         }
217
218         @Override
219         public String createSectionSeparator(T first, T second) {
220             boolean returnSeparator = false;
221             if (first == null) {
222                 returnSeparator = true;
223             } else {
224                 // create a separator if both album counts are different and they are
225                 // not greater than 5 albums
226                 int firstInt = getInt(first);
227                 int secondInt = getInt(second);
228                 if (firstInt != secondInt && 
229                         !(firstInt >= 5 && secondInt >= 5)) {
230                     returnSeparator = true;
231                 }
232             }
233
234             if (returnSeparator) {
235                 return createLabel(second);
236             }
237
238             return null;
239         }
240
241         @Override
242         protected String createLabel(int stringId, T item) {
243             if (stringId == R.string.header_n_albums) {
244                 return mContext.getString(stringId, getInt(item));
245             }
246
247             return super.createLabel(stringId, item);
248         }
249     }
250
251     /**
252      * This creates the sections give a list of items and the comparison algorithm
253      * @param list The list of items to analyze
254      * @param comparator The comparison function to use
255      * @param <T> the type of item to compare
256      * @return Creates a TreeMap of indices (if the headers were part of the list) to section labels
257      */
258     public static <T> TreeMap<Integer, String> createSections(final List<T> list,
259                                                               final IItemCompare<T> comparator) {
260         if (list != null && list.size() > 0) {
261             TreeMap<Integer, String> sectionHeaders = new TreeMap<Integer, String>();
262             for (int i = 0; i < list.size(); i++) {
263                 T first = (i == 0 ? null : list.get(i - 1));
264                 T second = list.get(i);
265
266                 String separator = comparator.createSectionSeparator(first, second);
267                 if (separator != null) {
268                     // add sectionHeaders.size() to store the indices of the combined list
269                     sectionHeaders.put(sectionHeaders.size() + i, separator);
270                 }
271             }
272
273             return sectionHeaders;
274         }
275
276         return null;
277     }
278
279     /**
280      * Returns an artist comparison based on the current sort
281      * @param context Context for string generation
282      * @return the artist comparison method
283      */
284     public static IItemCompare<Artist> createArtistComparison(final Context context) {
285         IItemCompare<Artist> sectionCreator = null;
286
287         String sortOrder = PreferenceUtils.getInstance(context).getArtistSortOrder();
288         if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z)
289                 || sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A)) {
290             sectionCreator = new SectionCreatorUtils.LocalizedCompare<Artist>() {
291                 @Override
292                 public String getString(Artist item) {
293                     return item.mArtistName;
294                 }
295             };
296         } else if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_NUMBER_OF_ALBUMS)) {
297             sectionCreator = new SectionCreatorUtils.NumberOfAlbumsCompare<Artist>(context) {
298                 @Override
299                 public int getInt(Artist item) {
300                     return item.mAlbumNumber;
301                 }
302             };
303         } else if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_NUMBER_OF_SONGS)) {
304             sectionCreator = new NumberOfSongsCompare<Artist>(context) {
305                 @Override
306                 public int getInt(Artist item) {
307                     return item.mSongNumber;
308                 }
309             };
310         }
311
312         return sectionCreator;
313     }
314
315     /**
316      * Returns an album comparison based on the current sort
317      * @param context Context for string generation
318      * @return the album comparison method
319      */
320     public static IItemCompare<Album> createAlbumComparison(final Context context) {
321         IItemCompare<Album> sectionCreator = null;
322
323         String sortOrder = PreferenceUtils.getInstance(context).getAlbumSortOrder();
324         if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z)
325                 || sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A)) {
326             sectionCreator = new LocalizedCompare<Album>() {
327                 @Override
328                 public String getString(Album item) {
329                     return item.mAlbumName;
330                 }
331             };
332         } else if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST)) {
333             sectionCreator = new LocalizedCompare<Album>() {
334                 @Override
335                 public String getString(Album item) {
336                     return item.mArtistName;
337                 }
338
339                 @Override
340                 public boolean trimName() {
341                     return false;
342                 }
343             };
344         } else if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_NUMBER_OF_SONGS)) {
345             sectionCreator = new NumberOfSongsCompare<Album>(context) {
346                 @Override
347                 public int getInt(Album item) {
348                     return item.mSongNumber;
349                 }
350             };
351         } else if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_YEAR)) {
352             sectionCreator = new IntCompare<Album>() {
353                 private static final int INVALID_YEAR = -1;
354
355                 @Override
356                 public int getInt(Album item) {
357                     // if we don't have a year, treat it as invalid
358                     if (item.mYear == null) {
359                         return INVALID_YEAR;
360                     }
361
362                     int year = Integer.valueOf(item.mYear);
363
364                     // if the year is extremely low, treat it as invalid too
365                     if (MusicUtils.isInvalidYear(year)) {
366                         return INVALID_YEAR;
367                     }
368
369                     return year;
370                 }
371
372                 @Override
373                 public String createLabel(Album item) {
374                     if (MusicUtils.isInvalidYear(getInt(item))) {
375                         return context.getString(R.string.header_unknown_year);
376                     }
377
378                     return item.mYear;
379                 }
380             };
381         }
382
383         return sectionCreator;
384     }
385
386     /**
387      * Returns an song comparison based on the current sort
388      * @param context Context for string generation
389      * @return the song comparison method
390      */
391     public static IItemCompare<Song> createSongComparison(final Context context) {
392         IItemCompare<Song> sectionCreator = null;
393
394         String sortOrder = PreferenceUtils.getInstance(context).getSongSortOrder();
395
396         // doesn't make sense to have headers for SONG_FILENAME
397         // so we will not return a sectionCreator for that one
398         if (sortOrder.equals(SortOrder.SongSortOrder.SONG_A_Z)
399                 || sortOrder.equals(SortOrder.SongSortOrder.SONG_Z_A)) {
400             sectionCreator = new LocalizedCompare<Song>() {
401                 @Override
402                 public String getString(Song item) {
403                     return item.mSongName;
404                 }
405             };
406         } else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_ALBUM)) {
407             sectionCreator = new LocalizedCompare<Song>() {
408                 @Override
409                 public String getString(Song item) {
410                     return item.mAlbumName;
411                 }
412             };
413         } else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_ARTIST)) {
414             sectionCreator = new LocalizedCompare<Song>() {
415                 @Override
416                 public String getString(Song item) {
417                     return item.mArtistName;
418                 }
419
420                 @Override
421                 public boolean trimName() {
422                     return false;
423                 }
424             };
425         } else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_DURATION)) {
426             sectionCreator = new DurationCompare<Song>(context) {
427                 @Override
428                 public int getInt(Song item) {
429                     return item.mDuration;
430                 }
431             };
432         } else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_YEAR)) {
433             sectionCreator = new SectionCreatorUtils.IntCompare<Song>() {
434                 @Override
435                 public int getInt(Song item) {
436                     return item.mYear;
437                 }
438
439                 @Override
440                 public String createLabel(Song item) {
441                     // I have seen tracks in my library where it would return 0 or 2
442                     // so have this check to return a more friendly label in that case
443                     if (MusicUtils.isInvalidYear(item.mYear)) {
444                         return context.getString(R.string.header_unknown_year);
445                     }
446
447                     return super.createLabel(item);
448                 }
449             };
450         }
451
452         return sectionCreator;
453     }
454 }