diff options
-rw-r--r-- | res/drawable/inset_list_divider_no_padding.xml | 24 | ||||
-rw-r--r-- | res/layout/list_base.xml | 2 | ||||
-rw-r--r-- | res/layout/list_base_nopadding.xml | 2 | ||||
-rw-r--r-- | res/layout/list_header.xml | 6 | ||||
-rw-r--r-- | res/layout/list_item_simple.xml | 4 | ||||
-rw-r--r-- | res/layout/list_search_footer.xml | 26 | ||||
-rw-r--r-- | res/layout/list_search_header.xml | 34 | ||||
-rw-r--r-- | res/values/colors.xml | 3 | ||||
-rw-r--r-- | res/values/dimens.xml | 8 | ||||
-rw-r--r-- | res/values/strings.xml | 12 | ||||
-rw-r--r-- | res/values/styles.xml | 8 | ||||
-rw-r--r-- | src/com/cyngn/eleven/Config.java | 5 | ||||
-rw-r--r-- | src/com/cyngn/eleven/adapters/SummarySearchAdapter.java | 31 | ||||
-rw-r--r-- | src/com/cyngn/eleven/sectionadapter/SectionAdapter.java | 110 | ||||
-rw-r--r-- | src/com/cyngn/eleven/sectionadapter/SectionCreator.java | 6 | ||||
-rw-r--r-- | src/com/cyngn/eleven/sectionadapter/SectionListContainer.java | 9 | ||||
-rw-r--r-- | src/com/cyngn/eleven/ui/activities/BaseActivity.java | 2 | ||||
-rw-r--r-- | src/com/cyngn/eleven/ui/activities/SearchActivity.java | 295 | ||||
-rw-r--r-- | src/com/cyngn/eleven/utils/SectionCreatorUtils.java | 169 |
19 files changed, 599 insertions, 157 deletions
diff --git a/res/drawable/inset_list_divider_no_padding.xml b/res/drawable/inset_list_divider_no_padding.xml new file mode 100644 index 0000000..763868f --- /dev/null +++ b/res/drawable/inset_list_divider_no_padding.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (C) 2014 The CyanogenMod Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<inset xmlns:android="http://schemas.android.com/apk/res/android"> + + <shape> + <solid android:color="@color/list_item_divider_color" /> + <corners android:radius="1.0dip" /> + </shape> + +</inset>
\ No newline at end of file diff --git a/res/layout/list_base.xml b/res/layout/list_base.xml index bb70e39..a365b1e 100644 --- a/res/layout/list_base.xml +++ b/res/layout/list_base.xml @@ -30,6 +30,6 @@ android:drawSelectorOnTop="false" android:fadingEdge="vertical" android:fastScrollEnabled="true" - android:dividerHeight="1dp" + android:dividerHeight="@dimen/divider_height" android:divider="@drawable/inset_list_divider" /> </FrameLayout>
\ No newline at end of file diff --git a/res/layout/list_base_nopadding.xml b/res/layout/list_base_nopadding.xml index 0ba5af1..87ed176 100644 --- a/res/layout/list_base_nopadding.xml +++ b/res/layout/list_base_nopadding.xml @@ -30,6 +30,6 @@ android:drawSelectorOnTop="false" android:fadingEdge="vertical" android:fastScrollEnabled="true" - android:dividerHeight="1dp" + android:dividerHeight="@dimen/divider_height" android:divider="@drawable/dnd_list_divider"/> </FrameLayout>
\ No newline at end of file diff --git a/res/layout/list_header.xml b/res/layout/list_header.xml index 085fa93..68b590f 100644 --- a/res/layout/list_header.xml +++ b/res/layout/list_header.xml @@ -15,13 +15,15 @@ limitations under the License. --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/header" + android:id="@+id/title" android:layout_width="match_parent" - android:layout_height="@dimen/list_item_header_height" + android:layout_height="30dp" + android:layout_alignParentBottom="true" android:paddingLeft="@dimen/list_preferred_item_padding" android:paddingRight="@dimen/list_preferred_item_padding" android:background="@null" android:gravity="center_vertical" + android:textAllCaps="true" android:textColor="@color/list_item_header_text_color" android:textSize="@dimen/list_item_header_size" android:fontFamily="sans-serif-light" /> diff --git a/res/layout/list_item_simple.xml b/res/layout/list_item_simple.xml index 5552d56..1332594 100644 --- a/res/layout/list_item_simple.xml +++ b/res/layout/list_item_simple.xml @@ -20,7 +20,9 @@ android:gravity="center_vertical" android:minHeight="@dimen/item_normal_height" android:paddingBottom="@dimen/list_item_padding_bottom" - android:paddingTop="@dimen/list_item_padding_top"> + android:paddingTop="@dimen/list_item_padding_top" + android:paddingLeft="@dimen/list_preferred_item_padding" + android:paddingRight="@dimen/list_preferred_item_padding"> <!-- center the text views vertically --> <LinearLayout diff --git a/res/layout/list_search_footer.xml b/res/layout/list_search_footer.xml new file mode 100644 index 0000000..457b611 --- /dev/null +++ b/res/layout/list_search_footer.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2014 Cyanogen, Inc. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@null" + android:orientation="vertical"> + + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="50dp" + android:background="@null" + android:gravity="center" + android:textAllCaps="true" + android:textColor="@color/list_item_search_footer_text_color" + android:textSize="@dimen/list_item_footer_size" + android:textStyle="bold" /> + + <ImageView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:src="@drawable/inset_list_divider_no_padding" /> +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/list_search_header.xml b/res/layout/list_search_header.xml new file mode 100644 index 0000000..d0aa8d1 --- /dev/null +++ b/res/layout/list_search_header.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2012 Andrew Neal + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="46dp" + android:background="@null"> + <TextView + android:id="@+id/title" + android:layout_width="match_parent" + android:layout_height="20dp" + android:layout_alignParentBottom="true" + android:paddingLeft="@dimen/list_preferred_item_padding" + android:paddingRight="@dimen/list_preferred_item_padding" + android:background="@null" + android:gravity="center_vertical" + android:textAllCaps="true" + android:textColor="@color/list_item_header_text_color" + android:textSize="@dimen/list_item_header_size" + android:fontFamily="sans-serif-light" /> +</RelativeLayout>
\ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index aa34581..24072b6 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -80,6 +80,7 @@ <color name="smart_playlist_item_background">#1931353f</color> <color name="list_item_background">#4ce4e9ed</color> <color name="list_item_header_text_color">#ff3d4049</color> + <color name="list_item_search_footer_text_color">#ff41a4f4</color> <color name="list_item_text_color">@color/default_text_color</color> <color name="list_item_text_color_light">@color/default_text_color_light</color> <color name="list_item_divider_color">#4c231f20</color> @@ -90,6 +91,8 @@ <color name="tpi_text_color">#bf3d4049</color> <color name="tpi_selected_text_color">#bf3d4049</color> + <!-- Search Colors --> + <color name="search_hint_color">#4cffffff</color> <!-- Color for the text on the audio player --> <color name="audio_player_text_color">@color/default_text_color_light</color> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index af6d700..06189b5 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -31,9 +31,6 @@ <dimen name="overflow_height">30.0dip</dimen> <dimen name="overflow_width">24.0dip</dimen> - <!-- List item section header --> - <dimen name="list_item_header_height">30.0dip</dimen> - <!-- List and grid view padding --> <dimen name="list_preferred_item_padding">10.0dip</dimen> <dimen name="list_menu_item_padding_right">2.0dip</dimen> @@ -42,6 +39,7 @@ <dimen name="list_item_padding_top">10.0dip</dimen> <dimen name="list_item_padding_bottom">10.0dip</dimen> <dimen name="list_item_header_size">16.0sp</dimen> + <dimen name="list_item_footer_size">20.0sp</dimen> <dimen name="list_item_main_text_size">@dimen/text_size_small</dimen> <dimen name="list_item_secondary_text_size">@dimen/text_size_micro</dimen> <dimen name="list_item_queue_text_padding_left">15.0dip</dimen> @@ -189,4 +187,8 @@ <dimen name="no_results_text_padding_bottom">16.0dip</dimen> <dimen name="no_reuslts_text_main">20.0sp</dimen> <dimen name="no_results_text_secondary">14.0sp</dimen> + + <!-- General consensus is to leave dividers at 1px instead of having different + partial scaled up values for different resolutions --> + <dimen name="divider_height">1px</dimen> </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index 3356d91..f49af10 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -18,7 +18,6 @@ <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name" translatable="false">Music</string> - <string name="app_name_uppercase" translatable="false">MUSIC</string> <!-- Page titles --> <string name="page_recent">Recent</string> @@ -211,6 +210,17 @@ <string name="header_n_albums"><xliff:g id="number">%d</xliff:g> Albums</string> <string name="header_5_plus_albums">5+ Albums</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="footer_search_playlists">Show all playlists</string> + + <string name="searchHint">Search Music</string> + <string name="search_title_artists">All \"%s\" artists</string> + <string name="search_title_albums">All \"%s\" albums</string> + <string name="search_title_songs">All \"%s\" songs</string> + <string name="search_title_playlists">All \"%s\" playlists</string> + <string name="duration_album_mins_only"><xliff:g id="format">%1$dm</xliff:g></string> <string name="duration_album_hour_mins"><xliff:g id="format">%1$dh %2$dm</xliff:g></string> diff --git a/res/values/styles.xml b/res/values/styles.xml index 3e1ec0f..7d11d0f 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -30,8 +30,8 @@ </style> <style name="ActionBarWidgetTheme" parent="@android:style/Theme.Holo"> - <!-- This is the color of the search text in the action bar --> - <item name="android:textColorHint">@android:color/white</item> + <!-- This is the color of the search text hint in the action bar --> + <item name="android:textColorHint">@color/search_hint_color</item> <item name="android:popupMenuStyle">@style/PopupMenu</item> <item name="android:dropDownListViewStyle">@style/DropDownListView</item> <item name="android:textAppearanceSmallPopupMenu">@style/SmallPopupMenu</item> @@ -46,7 +46,7 @@ <!-- Set the default list view divider color and size --> <style name="ListView" parent="@android:style/Widget.Holo.ListView"> <item name="android:divider">@color/list_item_divider_color</item> - <item name="android:dividerHeight">1dp</item> + <item name="android:dividerHeight">@dimen/divider_height</item> </style> <!-- Sets up the pop up menu backgroudn resource --> @@ -57,7 +57,7 @@ <!-- Sets up the pop up menu divider color and height --> <style name="DropDownListView" parent="@android:style/Widget.Holo.ListView.DropDown"> <item name="android:divider">@color/menu_divider_color</item> - <item name="android:dividerHeight">1dp</item> + <item name="android:dividerHeight">@dimen/divider_height</item> </style> <!-- Sets up the pop up menu text color and size --> diff --git a/src/com/cyngn/eleven/Config.java b/src/com/cyngn/eleven/Config.java index 8fb973e..af8f1ee 100644 --- a/src/com/cyngn/eleven/Config.java +++ b/src/com/cyngn/eleven/Config.java @@ -72,6 +72,11 @@ public final class Config { */ public static final String SMART_PLAYLIST_TYPE = "smart_playlist_type"; + /** + * Number of search results to show at the top level search + */ + public static final int SEARCH_NUM_RESULTS_TO_GET = 3; + public static enum SmartPlaylistType { LastAdded(-1), RecentlyPlayed(-2), diff --git a/src/com/cyngn/eleven/adapters/SummarySearchAdapter.java b/src/com/cyngn/eleven/adapters/SummarySearchAdapter.java index 2f3c7a1..4673164 100644 --- a/src/com/cyngn/eleven/adapters/SummarySearchAdapter.java +++ b/src/com/cyngn/eleven/adapters/SummarySearchAdapter.java @@ -9,6 +9,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; +import android.widget.TextView; import com.cyngn.eleven.R; import com.cyngn.eleven.cache.ImageFetcher; @@ -27,7 +28,7 @@ import java.util.Locale; public final class SummarySearchAdapter extends ArrayAdapter<SearchResult> implements SectionAdapter.BasicAdapter { /** - * Number of views (ImageView and TextView) + * no-image list item type and with image type */ private static final int VIEW_TYPE_COUNT = 2; @@ -83,7 +84,7 @@ public final class SummarySearchAdapter extends ArrayAdapter<SearchResult> imple // Asynchronously load the artist image into the adapter mImageFetcher.loadArtistImage(item.mArtist, holder.mImage.get()); - mHighlighter.setText(holder.mLineOne.get(), item.mArtist, mPrefix); + setText(holder.mLineOne.get(), item.mArtist); String songCount = MusicUtils.makeLabel(getContext(), R.plurals.Nsongs, item.mSongCount); String albumCount = MusicUtils.makeLabel(getContext(), R.plurals.Nalbums, item.mAlbumCount); @@ -96,21 +97,20 @@ public final class SummarySearchAdapter extends ArrayAdapter<SearchResult> imple mImageFetcher.loadAlbumImage(item.mArtist, item.mAlbum, item.mId, holder.mImage.get()); - mHighlighter.setText(holder.mLineOne.get(), item.mAlbum, mPrefix); - mHighlighter.setText(holder.mLineTwo.get(), item.mArtist, mPrefix); + setText(holder.mLineOne.get(), item.mAlbum); + setText(holder.mLineTwo.get(), item.mArtist); break; case Song: // Asynchronously load the album images into the adapter mImageFetcher.loadAlbumImage(item.mArtist, item.mAlbum, item.mAlbumId, holder.mImage.get()); - mHighlighter.setText(holder.mLineOne.get(), item.mTitle, mPrefix); - mHighlighter.setText(holder.mLineTwo.get(), - MusicUtils.makeCombinedString(getContext(), item.mArtist, item.mAlbum), - mPrefix); + setText(holder.mLineOne.get(), item.mTitle); + setText(holder.mLineTwo.get(), + MusicUtils.makeCombinedString(getContext(), item.mArtist, item.mAlbum)); break; case Playlist: - mHighlighter.setText(holder.mLineOne.get(), item.mTitle, mPrefix); + setText(holder.mLineOne.get(), item.mTitle); String songs = MusicUtils.makeLabel(getContext(), R.plurals.Nsongs, item.mSongCount); holder.mLineTwo.get().setText(songs); holder.mLineThree.get().setVisibility(View.GONE); @@ -121,6 +121,19 @@ public final class SummarySearchAdapter extends ArrayAdapter<SearchResult> imple } /** + * Sets the text onto the textview with highlighting if a prefix is defined + * @param textView + * @param text + */ + private void setText(final TextView textView, final String text) { + if (mPrefix == null) { + textView.setText(text); + } else { + mHighlighter.setText(textView, text, mPrefix); + } + } + + /** * {@inheritDoc} */ @Override diff --git a/src/com/cyngn/eleven/sectionadapter/SectionAdapter.java b/src/com/cyngn/eleven/sectionadapter/SectionAdapter.java index a029c07..49e7b16 100644 --- a/src/com/cyngn/eleven/sectionadapter/SectionAdapter.java +++ b/src/com/cyngn/eleven/sectionadapter/SectionAdapter.java @@ -13,6 +13,8 @@ import android.widget.BaseAdapter; import android.widget.TextView; import com.cyngn.eleven.R; +import com.cyngn.eleven.utils.SectionCreatorUtils.Section; +import com.cyngn.eleven.utils.SectionCreatorUtils.SectionType; import java.util.TreeMap; @@ -42,9 +44,15 @@ public class SectionAdapter<TItem, protected TArrayAdapter mUnderlyingAdapter; /** - * A map of external position to the String to use as the header + * A map of external position to the Section type and Identifier */ - protected TreeMap<Integer, String> mSectionHeaders; + protected TreeMap<Integer, Section> mSections; + + protected int mHeaderLayoutId; + protected boolean mHeaderEnabled; + + protected int mFooterLayoutId; + protected boolean mFooterEnabled; /** * {@link Context} @@ -59,7 +67,10 @@ public class SectionAdapter<TItem, public SectionAdapter(final Activity context, final TArrayAdapter underlyingAdapter) { mContext = context; mUnderlyingAdapter = underlyingAdapter; - mSectionHeaders = new TreeMap<Integer, String>(); + mSections = new TreeMap<Integer, Section>(); + setupHeaderParameters(R.layout.list_header, false); + // since we have no good default footer, just re-use the header layout + setupFooterParameters(R.layout.list_header, false); } /** @@ -75,13 +86,18 @@ public class SectionAdapter<TItem, */ @Override public View getView(final int position, View convertView, final ViewGroup parent) { - if (isSectionHeader(position)) { + if (isSection(position)) { if (convertView == null) { - convertView = LayoutInflater.from(mContext).inflate(R.layout.list_header, parent, false); + int layoutId = mHeaderLayoutId; + if (isSectionFooter(position)) { + layoutId = mFooterLayoutId; + } + + convertView = LayoutInflater.from(mContext).inflate(layoutId, parent, false); } - TextView header = (TextView)convertView.findViewById(R.id.header); - header.setText(mSectionHeaders.get(position)); + TextView title = (TextView)convertView.findViewById(R.id.title); + title.setText(mSections.get(position).mIdentifier); } else { convertView = mUnderlyingAdapter.getView(getInternalPosition(position), convertView, parent); } @@ -90,11 +106,31 @@ public class SectionAdapter<TItem, } /** + * Setup the header parameters + * @param layoutId the layout id used to inflate + * @param enabled whether clicking is enabled on the header + */ + public void setupHeaderParameters(int layoutId, boolean enabled) { + mHeaderLayoutId = layoutId; + mHeaderEnabled = enabled; + } + + /** + * Setup the footer parameters + * @param layoutId the layout id used to inflate + * @param enabled whether clicking is enabled on the footer + */ + public void setupFooterParameters(int layoutId, boolean enabled) { + mFooterLayoutId = layoutId; + mFooterEnabled = enabled; + } + + /** * {@inheritDoc} */ @Override public int getCount() { - return mSectionHeaders.size() + mUnderlyingAdapter.getCount(); + return mSections.size() + mUnderlyingAdapter.getCount(); } /** @@ -102,8 +138,8 @@ public class SectionAdapter<TItem, */ @Override public Object getItem(int position) { - if (isSectionHeader(position)) { - return mSectionHeaders.get(position); + if (isSection(position)) { + return mSections.get(position); } return mUnderlyingAdapter.getItem(getInternalPosition(position)); @@ -115,7 +151,7 @@ public class SectionAdapter<TItem, * @return the underlying item or null if a section header is queried */ public TItem getTItem(int position) { - if (isSectionHeader(position)) { + if (isSection(position)) { return null; } @@ -146,6 +182,9 @@ public class SectionAdapter<TItem, if (isSectionHeader(position)) { // use the last view type id as the section header return getViewTypeCount() - 1; + } else if (isSectionFooter(position)) { + // use the last view type id as the section header + return getViewTypeCount() - 2; } return mUnderlyingAdapter.getItemViewType(getInternalPosition(position)); @@ -156,8 +195,8 @@ public class SectionAdapter<TItem, */ @Override public int getViewTypeCount() { - // increment view type count by 1 for section headers - return mUnderlyingAdapter.getViewTypeCount() + 1; + // increment view type count by 2 for section headers and section footers + return mUnderlyingAdapter.getViewTypeCount() + 2; } /** @@ -185,8 +224,13 @@ public class SectionAdapter<TItem, */ @Override public boolean isEnabled(int position) { - // don't enable clicking/long press for section headers - return !isSectionHeader(position); + if (isSectionHeader(position)) { + return mHeaderEnabled; + } else if (isSectionFooter(position)) { + return mFooterEnabled; + } + + return true; } /** @@ -202,8 +246,26 @@ public class SectionAdapter<TItem, * @param position position in the overall lis * @return true if a section header */ - private boolean isSectionHeader(int position) { - return mSectionHeaders.containsKey(position); + public boolean isSectionHeader(int position) { + return mSections.containsKey(position) && mSections.get(position).mType == SectionType.Header; + } + + /** + * Determines whether the item at the position is a section footer + * @param position position in the overall lis + * @return true if a section footer + */ + public boolean isSectionFooter(int position) { + return mSections.containsKey(position) && mSections.get(position).mType == SectionType.Footer; + } + + /** + * Determines whether the item at the position is a section of some type + * @param position position in the overall lis + * @return true if the item is a section + */ + public boolean isSection(int position) { + return mSections.containsKey(position); } /** @@ -213,13 +275,13 @@ public class SectionAdapter<TItem, * @return the internal position */ public int getInternalPosition(int position) { - if (isSectionHeader(position)) { + if (isSection(position)) { return -1; } int countSectionHeaders = 0; - for (Integer sectionPosition : mSectionHeaders.keySet()) { + for (Integer sectionPosition : mSections.keySet()) { if (sectionPosition <= position) { countSectionHeaders++; } else { @@ -237,7 +299,7 @@ public class SectionAdapter<TItem, */ public int getExternalPosition(int internalPosition) { int externalPosition = internalPosition; - for (Integer sectionPosition : mSectionHeaders.keySet()) { + for (Integer sectionPosition : mSections.keySet()) { // because the section headers are tracking the 'merged' lists, we need to keep bumping // our position for each found section header if (sectionPosition <= externalPosition) { @@ -257,10 +319,10 @@ public class SectionAdapter<TItem, public void setData(SectionListContainer<TItem> data) { mUnderlyingAdapter.unload(); - if (data.mSectionIndices == null) { - mSectionHeaders.clear(); + if (data.mSections == null) { + mSections.clear(); } else { - mSectionHeaders = data.mSectionIndices; + mSections = data.mSections; } mUnderlyingAdapter.addAll(data.mListResults); @@ -288,7 +350,7 @@ public class SectionAdapter<TItem, public void clear() { mUnderlyingAdapter.clear(); - mSectionHeaders.clear(); + mSections.clear(); } /** diff --git a/src/com/cyngn/eleven/sectionadapter/SectionCreator.java b/src/com/cyngn/eleven/sectionadapter/SectionCreator.java index 9e5a5f5..36fe74c 100644 --- a/src/com/cyngn/eleven/sectionadapter/SectionCreator.java +++ b/src/com/cyngn/eleven/sectionadapter/SectionCreator.java @@ -56,12 +56,12 @@ public class SectionCreator<T> extends WrappedAsyncTaskLoader<SectionListContain @Override public SectionListContainer<T> loadInBackground() { List<T> results = mLoader.loadInBackground(); - TreeMap<Integer, String> sectionHeaders = null; + TreeMap<Integer, SectionCreatorUtils.Section> sections = null; if (mComparator != null) { - sectionHeaders = SectionCreatorUtils.createSections(results, mComparator); + sections = SectionCreatorUtils.createSections(results, mComparator); } - return new SectionListContainer<T>(sectionHeaders, results); + return new SectionListContainer<T>(sections, results); } } diff --git a/src/com/cyngn/eleven/sectionadapter/SectionListContainer.java b/src/com/cyngn/eleven/sectionadapter/SectionListContainer.java index 56eb169..a5fe678 100644 --- a/src/com/cyngn/eleven/sectionadapter/SectionListContainer.java +++ b/src/com/cyngn/eleven/sectionadapter/SectionListContainer.java @@ -3,6 +3,8 @@ */ package com.cyngn.eleven.sectionadapter; +import com.cyngn.eleven.utils.SectionCreatorUtils; + import java.util.List; import java.util.TreeMap; @@ -11,11 +13,12 @@ import java.util.TreeMap; * @param <T> the type of item that the list contains */ public class SectionListContainer<T> { - public TreeMap<Integer, String> mSectionIndices; + public TreeMap<Integer, SectionCreatorUtils.Section> mSections; public List<T> mListResults; - public SectionListContainer(TreeMap<Integer, String> sectionIndices, List<T> results) { - mSectionIndices = sectionIndices; + public SectionListContainer(final TreeMap<Integer, SectionCreatorUtils.Section> sections, + final List<T> results) { + mSections = sections; mListResults = results; } } diff --git a/src/com/cyngn/eleven/ui/activities/BaseActivity.java b/src/com/cyngn/eleven/ui/activities/BaseActivity.java index b6dc98f..24f451a 100644 --- a/src/com/cyngn/eleven/ui/activities/BaseActivity.java +++ b/src/com/cyngn/eleven/ui/activities/BaseActivity.java @@ -120,7 +120,7 @@ public abstract class BaseActivity extends FragmentActivity implements ServiceCo // Initialize the broadcast receiver mPlaybackStatus = new PlaybackStatus(this); - getActionBar().setTitle(getString(R.string.app_name_uppercase)); + getActionBar().setTitle(getString(R.string.app_name).toUpperCase()); // Set the layout setContentView(setContentView()); diff --git a/src/com/cyngn/eleven/ui/activities/SearchActivity.java b/src/com/cyngn/eleven/ui/activities/SearchActivity.java index 79f0671..aede621 100644 --- a/src/com/cyngn/eleven/ui/activities/SearchActivity.java +++ b/src/com/cyngn/eleven/ui/activities/SearchActivity.java @@ -16,8 +16,10 @@ import android.app.SearchManager; import android.app.SearchableInfo; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.ServiceConnection; import android.database.Cursor; +import android.graphics.Bitmap; import android.media.AudioManager; import android.os.Bundle; import android.os.IBinder; @@ -31,16 +33,20 @@ import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AbsListView; import android.widget.AbsListView.OnScrollListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.SearchView; import android.widget.SearchView.OnQueryTextListener; import android.widget.TextView; +import com.cyngn.eleven.Config; import com.cyngn.eleven.IElevenService; import com.cyngn.eleven.R; import com.cyngn.eleven.adapters.SummarySearchAdapter; @@ -111,6 +117,17 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< private SectionAdapter<SearchResult, SummarySearchAdapter> mAdapter; /** + * boolean tracking whether this is the search level when the user first enters search + * or if the user has clicked show all + */ + private boolean mTopLevelSearch; + + /** + * If the user has clicked show all, this tells us what type (Artist, Album, etc) + */ + private ResultType mSearchType; + + /** * {@inheritDoc} */ @Override @@ -123,11 +140,6 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< // Control the media volume setVolumeControlStream(AudioManager.STREAM_MUSIC); - // Theme the action bar - final ActionBar actionBar = getActionBar(); - actionBar.setTitle(getString(R.string.app_name_uppercase)); - actionBar.setHomeButtonEnabled(true); - // Bind Apollo's service mToken = MusicUtils.bindToService(this, this); @@ -137,14 +149,13 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< // get the input method manager mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - // Get the query String - mFilterString = getIntent().getStringExtra(SearchManager.QUERY); - // Initialize the adapter SummarySearchAdapter adapter = new SummarySearchAdapter(this); mAdapter = new SectionAdapter<SearchResult, SummarySearchAdapter>(this, adapter); // Set the prefix mAdapter.getUnderlyingAdapter().setPrefix(mFilterString); + mAdapter.setupHeaderParameters(R.layout.list_search_header, false); + mAdapter.setupFooterParameters(R.layout.list_search_footer, true); // setup the no results container mNoResultsContainer = (NoResultsContainer)findViewById(R.id.no_results_container); @@ -153,13 +164,50 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< initListView(); + // Theme the action bar + final ActionBar actionBar = getActionBar(); + actionBar.setHomeButtonEnabled(true); + actionBar.setIcon(R.drawable.ic_action_search); + + // Get the query String + mFilterString = getIntent().getStringExtra(SearchManager.QUERY); + + // if we have a non-empty search string, this is a 2nd lvl search if (mFilterString != null && !mFilterString.isEmpty()) { + mTopLevelSearch = false; + + // get the search type to filter by + int type = getIntent().getIntExtra(SearchManager.SEARCH_MODE, -1); + if (type >= 0 && type < ResultType.values().length) { + mSearchType = ResultType.values()[type]; + } + + int resourceId = 0; + switch (mSearchType) { + case Artist: + resourceId = R.string.search_title_artists; + break; + case Album: + resourceId = R.string.search_title_albums; + break; + case Playlist: + resourceId = R.string.search_title_playlists; + break; + case Song: + resourceId = R.string.search_title_songs; + break; + } + actionBar.setTitle(getString(resourceId, mFilterString).toUpperCase()); + actionBar.setDisplayHomeAsUpEnabled(true); + + // Set the prefix + mAdapter.getUnderlyingAdapter().setPrefix(mFilterString); + // Prepare the loader. Either re-connect with an existing one, // or start a new one. getSupportLoaderManager().initLoader(0, null, this); - - // Action bar subtitle - getActionBar().setSubtitle("\"" + mFilterString + "\""); + } else { + mTopLevelSearch = true; } // set the background on the root view @@ -194,8 +242,16 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< */ @Override public Loader<SectionListContainer<SearchResult>> onCreateLoader(final int id, final Bundle args) { - IItemCompare<SearchResult> comparator = SectionCreatorUtils.createSearchResultComparison(this); - return new SectionCreator<SearchResult>(this, new SummarySearchLoader(this, mFilterString), + IItemCompare<SearchResult> comparator = null; + + // if we are at the top level, create a comparator to separate the different types into + // their own sections (artists, albums, etc) + if (mTopLevelSearch) { + comparator = SectionCreatorUtils.createSearchResultComparison(this); + } + + return new SectionCreator<SearchResult>(this, + new SummarySearchLoader(this, mFilterString, mSearchType), comparator); } @@ -204,6 +260,11 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< */ @Override public boolean onCreateOptionsMenu(final Menu menu) { + // if we are not a top level search view, we do not need to create the search fields + if (!mTopLevelSearch) { + return super.onCreateOptionsMenu(menu); + } + // Search view getMenuInflater().inflate(R.menu.search, menu); @@ -211,6 +272,17 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< MenuItem searchItem = menu.findItem(R.id.menu_search); mSearchView = (SearchView)searchItem.getActionView(); mSearchView.setOnQueryTextListener(this); + mSearchView.setQueryHint(getString(R.string.searchHint).toUpperCase()); + + // The SearchView has no way for you to customize or get access to the search icon in a + // normal fashion, so we need to manually look for the icon and change the + // layout params to hide it + mSearchView.setIconifiedByDefault(false); + mSearchView.setIconified(false); + int searchButtonId = getResources().getIdentifier("android:id/search_mag_icon", null, null); + ImageView searchIcon = (ImageView)mSearchView.findViewById(searchButtonId); + searchIcon.setLayoutParams(new LinearLayout.LayoutParams(0, 0)); + searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { @@ -224,14 +296,8 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< } }); - if (mFilterString == null || mFilterString.isEmpty()) { - menu.findItem(R.id.menu_search).expandActionView(); - } + menu.findItem(R.id.menu_search).expandActionView(); - // Add voice search - final SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE); - final SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName()); - mSearchView.setSearchableInfo(searchableInfo); return super.onCreateOptionsMenu(menu); } @@ -289,9 +355,6 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< final SectionListContainer<SearchResult> data) { // Check for any errors if (data.mListResults.isEmpty()) { - // Set the empty text - mNoResultsContainer.setMainHighlightText("\"" + mFilterString + "\""); - // clear the adapter mAdapter.clear(); mListView.setVisibility(View.INVISIBLE); @@ -335,14 +398,11 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< public boolean onQueryTextSubmit(final String query) { if (TextUtils.isEmpty(query)) { mFilterString = ""; - getActionBar().setSubtitle(""); return false; } hideInputManager(); - // Action bar subtitle - getActionBar().setSubtitle("\"" + mFilterString + "\""); return true; } @@ -385,24 +445,34 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< @Override public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) { - SearchResult item = mAdapter.getTItem(position); - switch (item.mType) { - case Artist: - NavUtils.openArtistProfile(this, item.mArtist); - break; - case Album: - NavUtils.openAlbumProfile(this, item.mAlbum, item.mArtist, item.mId); - break; - case Playlist: - NavUtils.openPlaylist(this, item.mId, item.mTitle); - break; - case Song: - // If it's a song, play it and leave - final long[] list = new long[]{ - item.mId - }; - MusicUtils.playAll(this, list, 0, false); - break; + if (mAdapter.isSectionFooter(position)) { + // since a footer should be after a list item by definition, let's look up the type + // of the previous item + SearchResult item = mAdapter.getTItem(position - 1); + Intent intent = new Intent(this, SearchActivity.class); + intent.putExtra(SearchManager.QUERY, mFilterString); + intent.putExtra(SearchManager.SEARCH_MODE, item.mType.ordinal()); + startActivity(intent); + } else { + SearchResult item = mAdapter.getTItem(position); + switch (item.mType) { + case Artist: + NavUtils.openArtistProfile(this, item.mArtist); + break; + case Album: + NavUtils.openAlbumProfile(this, item.mAlbum, item.mArtist, item.mId); + break; + case Playlist: + NavUtils.openPlaylist(this, item.mId, item.mTitle); + break; + case Song: + // If it's a song, play it and leave + final long[] list = new long[]{ + item.mId + }; + MusicUtils.playAll(this, list, 0, false); + break; + } } } @@ -426,23 +496,119 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< * This class loads a search result summary of items */ private static final class SummarySearchLoader extends SimpleListLoader<SearchResult> { - private static final int NUM_RESULTS_TO_GET = 3; - private final String mQuery; + private final ResultType mSearchType; - public SummarySearchLoader(final Context context, final String query) { + public SummarySearchLoader(final Context context, final String query, + final ResultType searchType) { super(context); mQuery = query; + mSearchType = searchType; + } + + /** + * This creates a search result given the data at the cursor position + * @param cursor at the position for the item + * @param type the type of item to create + * @return the search result + */ + protected SearchResult createSearchResult(final Cursor cursor, ResultType type) { + SearchResult item = null; + + switch (type) { + case Playlist: + item = SearchResult.createPlaylistResult(cursor); + item.mSongCount = MusicUtils.getSongCountForPlaylist(getContext(), item.mId); + break; + case Song: + item = SearchResult.createSearchResult(cursor); + if (item != null) { + AlbumArtistDetails details = MusicUtils.getAlbumArtDetails(getContext(), item.mId); + if (details != null) { + item.mArtist = details.mArtistName; + item.mAlbum = details.mAlbumName; + item.mAlbumId = details.mAlbumId; + } + } + break; + case Album: + case Artist: + default: + item = SearchResult.createSearchResult(cursor); + break; + } + + return item; } @Override public List<SearchResult> loadInBackground() { + // if we are doing a specific type search, run that one + if (mSearchType != null && mSearchType != ResultType.Unknown) { + return runSearchForType(); + } + + return runGenericSearch(); + } + + /** + * This creates a search for a specific type given a filter string. This will return the + * full list of results that matches those two requirements + * @return the results for that search + */ + protected List<SearchResult> runSearchForType() { + ArrayList<SearchResult> results = new ArrayList<SearchResult>(); + Cursor cursor = null; + try { + if (mSearchType == ResultType.Playlist) { + cursor = makePlaylistSearchCursor(getContext(), mQuery); + } else { + cursor = ApolloUtils.createSearchQueryCursor(getContext(), mQuery); + } + + // pre-cache this index + final int mimeTypeIndex = cursor.getColumnIndex(MediaStore.Audio.Media.MIME_TYPE); + + if (cursor != null && cursor.moveToFirst()) { + do { + boolean addResult = true; + + if (mSearchType != ResultType.Playlist) { + // get the result type + ResultType type = ResultType.getResultType(cursor, mimeTypeIndex); + if (type != mSearchType) { + addResult = false; + } + } + + if (addResult) { + results.add(createSearchResult(cursor, mSearchType)); + } + } while (cursor.moveToNext()); + } + + } finally { + if (cursor != null) { + cursor.close(); + cursor = null; + } + } + + return results; + } + + /** + * This will run a search given a filter string and return the top NUM_RESULTS_TO_GET per + * type + * @return the results for that search + */ + public List<SearchResult> runGenericSearch() { ArrayList<SearchResult> results = new ArrayList<SearchResult>(); // number of types to query for final int numTypes = ResultType.getNumTypes(); // number of results we want - final int numResultsNeeded = NUM_RESULTS_TO_GET * numTypes; + final int numResultsNeeded = Config.SEARCH_NUM_RESULTS_TO_GET * numTypes; // current number of results we have int numResultsAdded = 0; @@ -455,21 +621,17 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< if (playlistCursor != null && playlistCursor.moveToFirst()) { do { // create the item - SearchResult item = SearchResult.createPlaylistResult(playlistCursor); - if (item != null) { - // get the song count - item.mSongCount = MusicUtils.getSongCountForPlaylist(getContext(), item.mId); - - /// add the results - numResultsAdded++; - results.add(item); - } - } while (playlistCursor.moveToNext() && numResultsAdded < NUM_RESULTS_TO_GET); + SearchResult item = createSearchResult(playlistCursor, ResultType.Playlist); + /// add the results + numResultsAdded++; + results.add(item); + } while (playlistCursor.moveToNext() + && numResultsAdded < Config.SEARCH_NUM_RESULTS_TO_GET); // because we deal with playlists separately, // just mark that we have the full # of playlists // so that logic later can quit out early if full - numResultsAdded = NUM_RESULTS_TO_GET; + numResultsAdded = Config.SEARCH_NUM_RESULTS_TO_GET; // close the cursor playlistCursor.close(); @@ -489,20 +651,11 @@ public class SearchActivity extends FragmentActivity implements LoaderCallbacks< ResultType type = ResultType.getResultType(cursor, mimeTypeIndex); // if we still need this type - if (numOfEachType[type.ordinal()] < NUM_RESULTS_TO_GET) { + if (numOfEachType[type.ordinal()] < Config.SEARCH_NUM_RESULTS_TO_GET) { // get the search result - SearchResult item = SearchResult.createSearchResult(cursor); - if (item != null) { - if (item.mType == ResultType.Song) { - // get the album art details - AlbumArtistDetails details = MusicUtils.getAlbumArtDetails(getContext(), item.mId); - if (details != null) { - item.mArtist = details.mArtistName; - item.mAlbum = details.mAlbumName; - item.mAlbumId = details.mAlbumId; - } - } + SearchResult item = createSearchResult(cursor, type); + if (item != null) { // add it results.add(item); numOfEachType[type.ordinal()]++; diff --git a/src/com/cyngn/eleven/utils/SectionCreatorUtils.java b/src/com/cyngn/eleven/utils/SectionCreatorUtils.java index 683c0f4..62e102a 100644 --- a/src/com/cyngn/eleven/utils/SectionCreatorUtils.java +++ b/src/com/cyngn/eleven/utils/SectionCreatorUtils.java @@ -5,6 +5,7 @@ package com.cyngn.eleven.utils; import android.content.Context; +import com.cyngn.eleven.Config; import com.cyngn.eleven.R; import com.cyngn.eleven.model.Album; import com.cyngn.eleven.model.Artist; @@ -19,44 +20,95 @@ import java.util.TreeMap; * a section should be created */ public class SectionCreatorUtils { + public enum SectionType { + Header, + Footer + } + + public static class Section { + public SectionType mType; + public String mIdentifier; + + public Section(final SectionType type, final String identifier) { + mType = type; + mIdentifier = identifier; + } + } + /** * Interface to compare two items and create labels * @param <T> type of item to compare */ - public static interface IItemCompare<T> { + public static class IItemCompare<T> { /** * Compares to items and returns a section divider T if there should * be a section divider between first and second * @param first the first element in the list. If null, it is checking to see * if we need a divider at the beginning of the list * @param second the second element in the list. + * @param items the source list of items that we are creating headers from + * @param firstIndex index of the first item we are looking at * @return String the expected separator label or null if none */ - public String createSectionSeparator(T first, T second); + public String createSectionHeader(T first, T second, List<T> items, int firstIndex) { + return createSectionHeader(first, second); + } + + public String createSectionHeader(T first, T second) { + return null; + } + + /** + * Compares to items and returns a section divider T if there should + * be a section divider between first and second + * @param first the first element in the list. + * @param second the second element in the list. If null, it is checking to see if we need + * a divider at the end of the list + * @param items the source list of items that we are creating footers from + * @param firstIndex index of the first item we are looking at + * @return String the expected separator label or null if none + */ + public String createSectionFooter(T first, T second, List<T> items, int firstIndex) { + return createSectionFooter(first, second); + } + + public String createSectionFooter(T first, T second) { + return null; + } /** * Returns the section label that corresponds to this item * @param item the item * @return the section label that this label falls under */ - public String createLabel(T item); - } + public String createHeaderLabel(T item) { + return null; + } + /** + * Returns the section label that corresponds to this item + * @param item the item + * @return the section label that this label falls under + */ + public String createFooterLabel(T item) { + return null; + } + } /** * A localized String comparison implementation of IItemCompare * @param <T> the type of item to compare */ - public static abstract class LocalizedCompare<T> implements IItemCompare<T> { + public static abstract class LocalizedCompare<T> extends IItemCompare<T> { @Override - public String createSectionSeparator(T first, T second) { - String secondLabel = createLabel(second); + 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) { return null; } - if (first == null || !secondLabel.equals(createLabel(first))) { + if (first == null || !secondLabel.equals(createHeaderLabel(first))) { return secondLabel; } @@ -64,7 +116,7 @@ public class SectionCreatorUtils { } @Override - public String createLabel(T item) { + public String createHeaderLabel(T item) { return MusicUtils.getLocalizedBucketLetter(getString(item), trimName()); } @@ -83,18 +135,18 @@ public class SectionCreatorUtils { * A simple int comparison implementation of IItemCompare * @param <T> the type of item to compare */ - public static abstract class IntCompare<T> implements IItemCompare<T> { + public static abstract class IntCompare<T> extends IItemCompare<T> { @Override - public String createSectionSeparator(T first, T second) { + public String createSectionHeader(T first, T second) { if (first == null || getInt(first) != getInt(second)) { - return createLabel(second); + return createHeaderLabel(second); } return null; } @Override - public String createLabel(T item) { + public String createHeaderLabel(T item) { return String.valueOf(getInt(item)); } @@ -117,7 +169,7 @@ public class SectionCreatorUtils { protected abstract int getStringId(int value); @Override - public String createSectionSeparator(T first, T second) { + public String createSectionHeader(T first, T second) { int secondStringId = getStringId(getInt(second)); if (first == null || getStringId(getInt(first)) != secondStringId) { return createLabel(secondStringId, second); @@ -131,7 +183,7 @@ public class SectionCreatorUtils { } @Override - public String createLabel(T item) { + public String createHeaderLabel(T item) { return createLabel(getStringId(getInt(item)), item); } } @@ -217,7 +269,7 @@ public class SectionCreatorUtils { } @Override - public String createSectionSeparator(T first, T second) { + public String createSectionHeader(T first, T second) { boolean returnSeparator = false; if (first == null) { returnSeparator = true; @@ -226,14 +278,14 @@ public class SectionCreatorUtils { // not greater than 5 albums int firstInt = getInt(first); int secondInt = getInt(second); - if (firstInt != secondInt && + if (firstInt != secondInt && !(firstInt >= 5 && secondInt >= 5)) { returnSeparator = true; } } if (returnSeparator) { - return createLabel(second); + return createHeaderLabel(second); } return null; @@ -256,22 +308,34 @@ public class SectionCreatorUtils { * @param <T> the type of item to compare * @return Creates a TreeMap of indices (if the headers were part of the list) to section labels */ - public static <T> TreeMap<Integer, String> createSections(final List<T> list, + public static <T> TreeMap<Integer, Section> createSections(final List<T> list, final IItemCompare<T> comparator) { if (list != null && list.size() > 0) { - TreeMap<Integer, String> sectionHeaders = new TreeMap<Integer, String>(); - for (int i = 0; i < list.size(); i++) { + TreeMap<Integer, Section> sections = new TreeMap<Integer, Section>(); + for (int i = 0; i < list.size() + 1; i++) { T first = (i == 0 ? null : list.get(i - 1)); - T second = list.get(i); + T second = (i == list.size() ? null : list.get(i)); + + // create the footer first because if we need both it should be footer,header,item + // not header,footer,item + if (first != null) { + String footer = comparator.createSectionFooter(first, second, list, i - 1); + if (footer != null) { + // add sectionHeaders.size() to store the indices of the combined list + sections.put(sections.size() + i, new Section(SectionType.Footer, footer)); + } + } - String separator = comparator.createSectionSeparator(first, second); - if (separator != null) { - // add sectionHeaders.size() to store the indices of the combined list - sectionHeaders.put(sectionHeaders.size() + i, separator); + if (second != null) { + String header = comparator.createSectionHeader(first, second, list, i - 1); + if (header != null) { + // add sectionHeaders.size() to store the indices of the combined list + sections.put(sections.size() + i, new Section(SectionType.Header, header)); + } } } - return sectionHeaders; + return sections; } return null; @@ -371,7 +435,7 @@ public class SectionCreatorUtils { } @Override - public String createLabel(Album item) { + public String createHeaderLabel(Album item) { if (MusicUtils.isInvalidYear(getInt(item))) { return context.getString(R.string.header_unknown_year); } @@ -438,14 +502,14 @@ public class SectionCreatorUtils { } @Override - public String createLabel(Song item) { + public String createHeaderLabel(Song item) { // I have seen tracks in my library where it would return 0 or 2 // so have this check to return a more friendly label in that case if (MusicUtils.isInvalidYear(item.mYear)) { return context.getString(R.string.header_unknown_year); } - return super.createLabel(item); + return super.createHeaderLabel(item); } }; } @@ -462,16 +526,16 @@ public class SectionCreatorUtils { return new IItemCompare<SearchResult>() { @Override - public String createSectionSeparator(SearchResult first, SearchResult second) { + public String createSectionHeader(SearchResult first, SearchResult second) { if (first == null || first.mType != second.mType) { - return createLabel(second); + return createHeaderLabel(second); } return null; } @Override - public String createLabel(SearchResult item) { + public String createHeaderLabel(SearchResult item) { switch (item.mType) { case Artist: return context.getString(R.string.page_artists); @@ -485,6 +549,45 @@ public class SectionCreatorUtils { return null; } + + @Override + public String createSectionFooter(SearchResult first, SearchResult second, + List<SearchResult> items, int firstIndex) { + if (second == null || + (first != null && first.mType != second.mType)) { + // if we don't have SEARCH_NUM_RESULTS_TO_GET # of the same type of items + // then we don't have enough to show the footer. For example, if we show 5 + // items but only the last 2 items are artists, that means we only have 2 + // so there is no point in showing the "Show All" footer + // We start from 1 because we don't need to count + // the first item itself + for (int i = 1; i < Config.SEARCH_NUM_RESULTS_TO_GET; i++) { + if (firstIndex - i < 0 || items.get(firstIndex - i).mType != first.mType) { + return null; + } + } + + return createFooterLabel(first); + } + + return null; + } + + @Override + public String createFooterLabel(SearchResult item) { + switch (item.mType) { + case Artist: + return context.getString(R.string.footer_search_artists); + case Album: + return context.getString(R.string.footer_search_albums); + case Song: + return context.getString(R.string.footer_search_songs); + case Playlist: + return context.getString(R.string.footer_search_playlists); + } + + return null; + } }; } } |