OSDN Git Service

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