summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--res/drawable/inset_list_divider_no_padding.xml24
-rw-r--r--res/layout/list_base.xml2
-rw-r--r--res/layout/list_base_nopadding.xml2
-rw-r--r--res/layout/list_header.xml6
-rw-r--r--res/layout/list_item_simple.xml4
-rw-r--r--res/layout/list_search_footer.xml26
-rw-r--r--res/layout/list_search_header.xml34
-rw-r--r--res/values/colors.xml3
-rw-r--r--res/values/dimens.xml8
-rw-r--r--res/values/strings.xml12
-rw-r--r--res/values/styles.xml8
-rw-r--r--src/com/cyngn/eleven/Config.java5
-rw-r--r--src/com/cyngn/eleven/adapters/SummarySearchAdapter.java31
-rw-r--r--src/com/cyngn/eleven/sectionadapter/SectionAdapter.java110
-rw-r--r--src/com/cyngn/eleven/sectionadapter/SectionCreator.java6
-rw-r--r--src/com/cyngn/eleven/sectionadapter/SectionListContainer.java9
-rw-r--r--src/com/cyngn/eleven/ui/activities/BaseActivity.java2
-rw-r--r--src/com/cyngn/eleven/ui/activities/SearchActivity.java295
-rw-r--r--src/com/cyngn/eleven/utils/SectionCreatorUtils.java169
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;
+ }
};
}
}