OSDN Git Service

62e102af9e524dba27b619c4a1794751f2a972b4
[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.Config;
9 import com.cyngn.eleven.R;
10 import com.cyngn.eleven.model.Album;
11 import com.cyngn.eleven.model.Artist;
12 import com.cyngn.eleven.model.SearchResult;
13 import com.cyngn.eleven.model.Song;
14
15 import java.util.List;
16 import java.util.TreeMap;
17
18 /**
19  * This Utils class contains code that compares two different items and determines whether
20  * a section should be created
21  */
22 public class SectionCreatorUtils {
23     public enum SectionType {
24         Header,
25         Footer
26     }
27
28     public static class Section {
29         public SectionType mType;
30         public String mIdentifier;
31
32         public Section(final SectionType type, final String identifier) {
33             mType = type;
34             mIdentifier = identifier;
35         }
36     }
37
38     /**
39      * Interface to compare two items and create labels
40      * @param <T> type of item to compare
41      */
42     public static class IItemCompare<T> {
43         /**
44          * Compares to items and returns a section divider T if there should
45          * be a section divider between first and second
46          * @param first the first element in the list.  If null, it is checking to see
47          *              if we need a divider at the beginning of the list
48          * @param second the second element in the list.
49          * @param items the source list of items that we are creating headers from
50          * @param firstIndex index of the first item we are looking at
51          * @return String the expected separator label or null if none
52          */
53         public String createSectionHeader(T first, T second, List<T> items, int firstIndex) {
54             return createSectionHeader(first, second);
55         }
56
57         public String createSectionHeader(T first, T second) {
58             return null;
59         }
60
61         /**
62          * Compares to items and returns a section divider T if there should
63          * be a section divider between first and second
64          * @param first the first element in the list.
65          * @param second the second element in the list. If null, it is checking to see if we need
66          *               a divider at the end of the list
67          * @param items the source list of items that we are creating footers from
68          * @param firstIndex index of the first item we are looking at
69          * @return String the expected separator label or null if none
70          */
71         public String createSectionFooter(T first, T second, List<T> items, int firstIndex) {
72             return createSectionFooter(first, second);
73         }
74
75         public String createSectionFooter(T first, T second) {
76             return null;
77         }
78
79         /**
80          * Returns the section label that corresponds to this item
81          * @param item the item
82          * @return the section label that this label falls under
83          */
84         public String createHeaderLabel(T item) {
85             return null;
86         }
87
88         /**
89          * Returns the section label that corresponds to this item
90          * @param item the item
91          * @return the section label that this label falls under
92          */
93         public String createFooterLabel(T item) {
94             return null;
95         }
96     }
97
98     /**
99      * A localized String comparison implementation of IItemCompare
100      * @param <T> the type of item to compare
101      */
102     public static abstract class LocalizedCompare<T> extends IItemCompare<T> {
103         @Override
104         public String createSectionHeader(T first, T second) {
105             String secondLabel = createHeaderLabel(second);
106             // if we can't determine a good label then don't bother creating a section
107             if (secondLabel == null) {
108                 return null;
109             }
110
111             if (first == null || !secondLabel.equals(createHeaderLabel(first))) {
112                 return secondLabel;
113             }
114
115             return null;
116         }
117
118         @Override
119         public String createHeaderLabel(T item) {
120             return MusicUtils.getLocalizedBucketLetter(getString(item), trimName());
121         }
122
123         /**
124          * @return true if we want to trim the name first - apparently artists don't trim
125          * but albums/songs do
126          */
127         public boolean trimName() {
128             return true;
129         }
130
131         public abstract String getString(T item);
132     }
133
134     /**
135      * A simple int comparison implementation of IItemCompare
136      * @param <T> the type of item to compare
137      */
138     public static abstract class IntCompare<T> extends IItemCompare<T> {
139         @Override
140         public String createSectionHeader(T first, T second) {
141             if (first == null || getInt(first) != getInt(second)) {
142                 return createHeaderLabel(second);
143             }
144
145             return null;
146         }
147
148         @Override
149         public String createHeaderLabel(T item) {
150             return String.valueOf(getInt(item));
151         }
152
153         public abstract int getInt(T item);
154     }
155
156     /**
157      * A Bounded int comparison implementation of IntCompare
158      * Basically this will take ints and determine what bounds it falls into
159      * For example, 1-5 mintes, 5-10 minutes, 10+ minutes
160      * @param <T> the type of item to compare
161      */
162     public static abstract class BoundedIntCompare<T> extends IntCompare<T> {
163         protected Context mContext;
164
165         public BoundedIntCompare(Context context) {
166             mContext = context;
167         }
168
169         protected abstract int getStringId(int value);
170
171         @Override
172         public String createSectionHeader(T first, T second) {
173             int secondStringId = getStringId(getInt(second));
174             if (first == null || getStringId(getInt(first)) != secondStringId) {
175                 return createLabel(secondStringId, second);
176             }
177
178             return null;
179         }
180
181         protected String createLabel(int stringId, T item) {
182             return mContext.getString(stringId);
183         }
184
185         @Override
186         public String createHeaderLabel(T item) {
187             return createLabel(getStringId(getInt(item)), item);
188         }
189     }
190
191     /**
192      * This implements BoundedIntCompare and gives duration buckets
193      * @param <T> the type of item to compare
194      */
195     public static abstract class DurationCompare<T> extends BoundedIntCompare<T> {
196         private static final int SECONDS_PER_MINUTE = 60;
197
198         public DurationCompare(Context context) {
199             super(context);
200         }
201
202         @Override
203         protected int getStringId(int value) {
204             if (value < 30) {
205                 return R.string.header_less_than_30s;
206             } else if (value < 1 * SECONDS_PER_MINUTE) {
207                 return R.string.header_30_to_60_seconds;
208             } else if (value < 2 * SECONDS_PER_MINUTE) {
209                 return R.string.header_1_to_2_minutes;
210             } else if (value < 3 * SECONDS_PER_MINUTE) {
211                 return R.string.header_2_to_3_minutes;
212             } else if (value < 4 * SECONDS_PER_MINUTE) {
213                 return R.string.header_3_to_4_minutes;
214             } else if (value < 5 * SECONDS_PER_MINUTE) {
215                 return R.string.header_4_to_5_minutes;
216             } else if (value < 10 * SECONDS_PER_MINUTE) {
217                 return R.string.header_5_to_10_minutes;
218             } else if (value < 30 * SECONDS_PER_MINUTE) {
219                 return R.string.header_10_to_30_minutes;
220             } else if (value < 60 * SECONDS_PER_MINUTE) {
221                 return R.string.header_30_to_60_minutes;
222             }
223
224             return R.string.header_greater_than_60_minutes;
225         }
226     }
227
228     /**
229      * This implements BoundedIntCompare and gives number of songs buckets
230      * @param <T> the type of item to compare
231      */
232     public static abstract class NumberOfSongsCompare<T> extends BoundedIntCompare<T> {
233         public NumberOfSongsCompare(Context context) {
234             super(context);
235         }
236
237         @Override
238         protected int getStringId(int value) {
239             if (value <= 1) {
240                 return R.string.header_1_song;
241             } else if (value <= 4) {
242                 return R.string.header_2_to_4_songs;
243             } else if (value <= 9) {
244                 return R.string.header_5_to_9_songs;
245             }
246
247             return R.string.header_10_plus_songs;
248         }
249     }
250
251     /**
252      * This implements BoundedIntCompare and gives number of albums buckets
253      * @param <T> the type of item to compare
254      */
255     public static abstract class NumberOfAlbumsCompare<T> extends BoundedIntCompare<T> {
256         public NumberOfAlbumsCompare(Context context) {
257             super(context);
258         }
259
260         @Override
261         protected int getStringId(int value) {
262             if (value <= 1) {
263                 return R.string.header_1_album;
264             } else if (value <= 4) {
265                 return R.string.header_n_albums;
266             }
267
268             return R.string.header_5_plus_albums;
269         }
270
271         @Override
272         public String createSectionHeader(T first, T second) {
273             boolean returnSeparator = false;
274             if (first == null) {
275                 returnSeparator = true;
276             } else {
277                 // create a separator if both album counts are different and they are
278                 // not greater than 5 albums
279                 int firstInt = getInt(first);
280                 int secondInt = getInt(second);
281                 if (firstInt != secondInt &&
282                         !(firstInt >= 5 && secondInt >= 5)) {
283                     returnSeparator = true;
284                 }
285             }
286
287             if (returnSeparator) {
288                 return createHeaderLabel(second);
289             }
290
291             return null;
292         }
293
294         @Override
295         protected String createLabel(int stringId, T item) {
296             if (stringId == R.string.header_n_albums) {
297                 return mContext.getString(stringId, getInt(item));
298             }
299
300             return super.createLabel(stringId, item);
301         }
302     }
303
304     /**
305      * This creates the sections give a list of items and the comparison algorithm
306      * @param list The list of items to analyze
307      * @param comparator The comparison function to use
308      * @param <T> the type of item to compare
309      * @return Creates a TreeMap of indices (if the headers were part of the list) to section labels
310      */
311     public static <T> TreeMap<Integer, Section> createSections(final List<T> list,
312                                                               final IItemCompare<T> comparator) {
313         if (list != null && list.size() > 0) {
314             TreeMap<Integer, Section> sections = new TreeMap<Integer, Section>();
315             for (int i = 0; i < list.size() + 1; i++) {
316                 T first = (i == 0 ? null : list.get(i - 1));
317                 T second = (i == list.size() ? null : list.get(i));
318
319                 // create the footer first because if we need both it should be footer,header,item
320                 // not header,footer,item
321                 if (first != null) {
322                     String footer = comparator.createSectionFooter(first, second, list, i - 1);
323                     if (footer != null) {
324                         // add sectionHeaders.size() to store the indices of the combined list
325                         sections.put(sections.size() + i, new Section(SectionType.Footer, footer));
326                     }
327                 }
328
329                 if (second != null) {
330                     String header = comparator.createSectionHeader(first, second, list, i - 1);
331                     if (header != null) {
332                         // add sectionHeaders.size() to store the indices of the combined list
333                         sections.put(sections.size() + i, new Section(SectionType.Header, header));
334                     }
335                 }
336             }
337
338             return sections;
339         }
340
341         return null;
342     }
343
344     /**
345      * Returns an artist comparison based on the current sort
346      * @param context Context for string generation
347      * @return the artist comparison method
348      */
349     public static IItemCompare<Artist> createArtistComparison(final Context context) {
350         IItemCompare<Artist> sectionCreator = null;
351
352         String sortOrder = PreferenceUtils.getInstance(context).getArtistSortOrder();
353         if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_A_Z)
354                 || sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_Z_A)) {
355             sectionCreator = new SectionCreatorUtils.LocalizedCompare<Artist>() {
356                 @Override
357                 public String getString(Artist item) {
358                     return item.mArtistName;
359                 }
360             };
361         } else if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_NUMBER_OF_ALBUMS)) {
362             sectionCreator = new SectionCreatorUtils.NumberOfAlbumsCompare<Artist>(context) {
363                 @Override
364                 public int getInt(Artist item) {
365                     return item.mAlbumNumber;
366                 }
367             };
368         } else if (sortOrder.equals(SortOrder.ArtistSortOrder.ARTIST_NUMBER_OF_SONGS)) {
369             sectionCreator = new NumberOfSongsCompare<Artist>(context) {
370                 @Override
371                 public int getInt(Artist item) {
372                     return item.mSongNumber;
373                 }
374             };
375         }
376
377         return sectionCreator;
378     }
379
380     /**
381      * Returns an album comparison based on the current sort
382      * @param context Context for string generation
383      * @return the album comparison method
384      */
385     public static IItemCompare<Album> createAlbumComparison(final Context context) {
386         IItemCompare<Album> sectionCreator = null;
387
388         String sortOrder = PreferenceUtils.getInstance(context).getAlbumSortOrder();
389         if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_A_Z)
390                 || sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_Z_A)) {
391             sectionCreator = new LocalizedCompare<Album>() {
392                 @Override
393                 public String getString(Album item) {
394                     return item.mAlbumName;
395                 }
396             };
397         } else if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_ARTIST)) {
398             sectionCreator = new LocalizedCompare<Album>() {
399                 @Override
400                 public String getString(Album item) {
401                     return item.mArtistName;
402                 }
403
404                 @Override
405                 public boolean trimName() {
406                     return false;
407                 }
408             };
409         } else if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_NUMBER_OF_SONGS)) {
410             sectionCreator = new NumberOfSongsCompare<Album>(context) {
411                 @Override
412                 public int getInt(Album item) {
413                     return item.mSongNumber;
414                 }
415             };
416         } else if (sortOrder.equals(SortOrder.AlbumSortOrder.ALBUM_YEAR)) {
417             sectionCreator = new IntCompare<Album>() {
418                 private static final int INVALID_YEAR = -1;
419
420                 @Override
421                 public int getInt(Album item) {
422                     // if we don't have a year, treat it as invalid
423                     if (item.mYear == null) {
424                         return INVALID_YEAR;
425                     }
426
427                     int year = Integer.valueOf(item.mYear);
428
429                     // if the year is extremely low, treat it as invalid too
430                     if (MusicUtils.isInvalidYear(year)) {
431                         return INVALID_YEAR;
432                     }
433
434                     return year;
435                 }
436
437                 @Override
438                 public String createHeaderLabel(Album item) {
439                     if (MusicUtils.isInvalidYear(getInt(item))) {
440                         return context.getString(R.string.header_unknown_year);
441                     }
442
443                     return item.mYear;
444                 }
445             };
446         }
447
448         return sectionCreator;
449     }
450
451     /**
452      * Returns an song comparison based on the current sort
453      * @param context Context for string generation
454      * @return the song comparison method
455      */
456     public static IItemCompare<Song> createSongComparison(final Context context) {
457         IItemCompare<Song> sectionCreator = null;
458
459         String sortOrder = PreferenceUtils.getInstance(context).getSongSortOrder();
460
461         // doesn't make sense to have headers for SONG_FILENAME
462         // so we will not return a sectionCreator for that one
463         if (sortOrder.equals(SortOrder.SongSortOrder.SONG_A_Z)
464                 || sortOrder.equals(SortOrder.SongSortOrder.SONG_Z_A)) {
465             sectionCreator = new LocalizedCompare<Song>() {
466                 @Override
467                 public String getString(Song item) {
468                     return item.mSongName;
469                 }
470             };
471         } else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_ALBUM)) {
472             sectionCreator = new LocalizedCompare<Song>() {
473                 @Override
474                 public String getString(Song item) {
475                     return item.mAlbumName;
476                 }
477             };
478         } else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_ARTIST)) {
479             sectionCreator = new LocalizedCompare<Song>() {
480                 @Override
481                 public String getString(Song item) {
482                     return item.mArtistName;
483                 }
484
485                 @Override
486                 public boolean trimName() {
487                     return false;
488                 }
489             };
490         } else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_DURATION)) {
491             sectionCreator = new DurationCompare<Song>(context) {
492                 @Override
493                 public int getInt(Song item) {
494                     return item.mDuration;
495                 }
496             };
497         } else if (sortOrder.equals(SortOrder.SongSortOrder.SONG_YEAR)) {
498             sectionCreator = new SectionCreatorUtils.IntCompare<Song>() {
499                 @Override
500                 public int getInt(Song item) {
501                     return item.mYear;
502                 }
503
504                 @Override
505                 public String createHeaderLabel(Song item) {
506                     // I have seen tracks in my library where it would return 0 or 2
507                     // so have this check to return a more friendly label in that case
508                     if (MusicUtils.isInvalidYear(item.mYear)) {
509                         return context.getString(R.string.header_unknown_year);
510                     }
511
512                     return super.createHeaderLabel(item);
513                 }
514             };
515         }
516
517         return sectionCreator;
518     }
519
520     /**
521      * Returns an song comparison based on the current sort
522      * @param context Context for string generation
523      * @return the song comparison method
524      */
525     public static IItemCompare<SearchResult> createSearchResultComparison(final Context context) {
526         return new IItemCompare<SearchResult>() {
527
528             @Override
529             public String createSectionHeader(SearchResult first, SearchResult second) {
530                 if (first == null || first.mType != second.mType) {
531                     return createHeaderLabel(second);
532                 }
533
534                 return null;
535             }
536
537             @Override
538             public String createHeaderLabel(SearchResult item) {
539                 switch (item.mType) {
540                     case Artist:
541                         return context.getString(R.string.page_artists);
542                     case Album:
543                         return context.getString(R.string.page_albums);
544                     case Song:
545                         return context.getString(R.string.page_songs);
546                     case Playlist:
547                         return context.getString(R.string.page_playlists);
548                 }
549
550                 return null;
551             }
552
553             @Override
554             public String createSectionFooter(SearchResult first, SearchResult second,
555                                               List<SearchResult> items, int firstIndex) {
556                 if (second == null ||
557                         (first != null && first.mType != second.mType)) {
558                     // if we don't have SEARCH_NUM_RESULTS_TO_GET # of the same type of items
559                     // then we don't have enough to show the footer.  For example, if we show 5
560                     // items but only the last 2 items are artists, that means we only have 2
561                     // so there is no point in showing the "Show All" footer
562                     // We start from 1 because we don't need to count
563                     // the first item itself
564                     for (int i = 1; i < Config.SEARCH_NUM_RESULTS_TO_GET; i++) {
565                         if (firstIndex - i < 0 || items.get(firstIndex - i).mType != first.mType) {
566                             return null;
567                         }
568                     }
569
570                     return createFooterLabel(first);
571                 }
572
573                 return null;
574             }
575
576             @Override
577             public String createFooterLabel(SearchResult item) {
578                 switch (item.mType) {
579                     case Artist:
580                         return context.getString(R.string.footer_search_artists);
581                     case Album:
582                         return context.getString(R.string.footer_search_albums);
583                     case Song:
584                         return context.getString(R.string.footer_search_songs);
585                     case Playlist:
586                         return context.getString(R.string.footer_search_playlists);
587                 }
588
589                 return null;
590             }
591         };
592     }
593 }