OSDN Git Service

Eleven: Update the search ui to be the latest version minus some features
authorlinus_lee <llee@cyngn.com>
Fri, 5 Sep 2014 01:09:11 +0000 (18:09 -0700)
committerlinus_lee <llee@cyngn.com>
Thu, 20 Nov 2014 20:03:02 +0000 (12:03 -0800)
this doesn't have the show all button or the jump into the new sections but it
looks much better than stock so we'll take it

Change-Id: I72a5d38c5fced13598000776d59a933b249262bf

28 files changed:
AndroidManifest.xml
res/drawable-hdpi/white_note.png [new file with mode: 0644]
res/drawable-mdpi/white_note.png [new file with mode: 0644]
res/drawable-xhdpi/white_note.png [new file with mode: 0644]
res/drawable-xxhdpi/white_note.png [new file with mode: 0644]
res/layout/search_layout.xml [new file with mode: 0644]
res/menu/search_btn.xml [new file with mode: 0644]
res/values/colors.xml
res/values/dimens.xml
res/values/strings.xml
res/values/styles.xml
src/com/cyngn/eleven/adapters/AlbumArtPagerAdapter.java
src/com/cyngn/eleven/adapters/SummarySearchAdapter.java [new file with mode: 0644]
src/com/cyngn/eleven/format/PrefixHighlighter.java
src/com/cyngn/eleven/loaders/PlaylistLoader.java
src/com/cyngn/eleven/model/AlbumArtistDetails.java [new file with mode: 0644]
src/com/cyngn/eleven/model/SearchResult.java [new file with mode: 0644]
src/com/cyngn/eleven/sectionadapter/SectionAdapter.java
src/com/cyngn/eleven/sectionadapter/SectionCreator.java
src/com/cyngn/eleven/ui/activities/BaseActivity.java
src/com/cyngn/eleven/ui/activities/ProfileActivity.java
src/com/cyngn/eleven/ui/activities/SearchActivity.java
src/com/cyngn/eleven/ui/activities/SettingsActivity.java
src/com/cyngn/eleven/ui/fragments/PlaylistFragment.java
src/com/cyngn/eleven/utils/ApolloUtils.java
src/com/cyngn/eleven/utils/MusicUtils.java
src/com/cyngn/eleven/utils/NavUtils.java
src/com/cyngn/eleven/utils/SectionCreatorUtils.java

index bcce99a..4ef165c 100644 (file)
         <!-- Search interface -->
         <activity
             android:name=".ui.activities.SearchActivity"
+            android:windowSoftInputMode="adjustResize"
             android:exported="true"
             android:screenOrientation="portrait">
             <intent-filter>
diff --git a/res/drawable-hdpi/white_note.png b/res/drawable-hdpi/white_note.png
new file mode 100644 (file)
index 0000000..d374770
Binary files /dev/null and b/res/drawable-hdpi/white_note.png differ
diff --git a/res/drawable-mdpi/white_note.png b/res/drawable-mdpi/white_note.png
new file mode 100644 (file)
index 0000000..d374770
Binary files /dev/null and b/res/drawable-mdpi/white_note.png differ
diff --git a/res/drawable-xhdpi/white_note.png b/res/drawable-xhdpi/white_note.png
new file mode 100644 (file)
index 0000000..d374770
Binary files /dev/null and b/res/drawable-xhdpi/white_note.png differ
diff --git a/res/drawable-xxhdpi/white_note.png b/res/drawable-xxhdpi/white_note.png
new file mode 100644 (file)
index 0000000..d374770
Binary files /dev/null and b/res/drawable-xxhdpi/white_note.png differ
diff --git a/res/layout/search_layout.xml b/res/layout/search_layout.xml
new file mode 100644 (file)
index 0000000..cae94c6
--- /dev/null
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/no_search_results"
+        android:layout_width="@dimen/no_results_width"
+        android:layout_height="wrap_content"
+        android:layout_centerInParent="true"
+        android:orientation="vertical"
+        android:visibility="gone">
+
+        <ImageView
+            android:layout_width="@dimen/white_note_width"
+            android:layout_height="@dimen/white_note_height"
+            android:layout_gravity="center_horizontal"
+            android:paddingBottom="@dimen/white_note_padding_bottom"
+            android:scaleType="centerInside"
+            android:src="@drawable/white_note" />
+
+        <TextView
+            android:gravity="center"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/empty_search"
+            android:textColor="@color/no_search_results"
+            android:textSize="@dimen/search_text_main"
+            android:textStyle="bold" />
+
+        <TextView
+            android:id="@+id/no_search_results_text"
+            android:gravity="center"
+            android:ellipsize="end"
+            android:layout_width="fill_parent"
+            android:layout_height="@dimen/no_results_text_height"
+            android:paddingBottom="@dimen/no_results_text_padding_bottom"
+            android:singleLine="true"
+            android:textColor="@color/no_search_results"
+            android:textSize="@dimen/search_text_main"
+            android:textStyle="bold|italic" />
+
+        <TextView
+            android:gravity="center"
+            android:layout_width="fill_parent"
+            android:layout_height="@dimen/no_results_text_height"
+            android:fontFamily="sans-serif-light"
+            android:text="@string/empty_search_check"
+            android:textColor="@color/no_search_results"
+            android:textSize="@dimen/search_text_secondary" />
+    </LinearLayout>
+
+    <include android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        layout="@layout/list_base" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/res/menu/search_btn.xml b/res/menu/search_btn.xml
new file mode 100644 (file)
index 0000000..02b604f
--- /dev/null
@@ -0,0 +1,24 @@
+<?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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:id="@+id/menu_search"
+        android:icon="@drawable/ic_action_search"
+        android:orderInCategory="1"
+        android:showAsAction="ifRoom"
+        android:title="@string/menu_search"/>
+</menu>
index 3d57335..62579cb 100644 (file)
@@ -91,4 +91,7 @@
     <!-- Color for the Progress bar -->
     <color name="circular_progress_bar_background">#bfababab</color>
     <color name="circular_progress_bar">@color/white</color>
+
+    <!-- search no results text color -->
+    <color name="no_search_results">#ff231f20</color>
 </resources>
index ad8a957..6437a23 100644 (file)
     <dimen name="list_item_progress_height">45.0dip</dimen>
     <dimen name="list_item_progress_padding_left">10.0dip</dimen>
     <dimen name="list_item_progress_padding_right">17.0dip</dimen>
+
+    <!-- Search Page Settings -->
+    <dimen name="white_note_width">60.0dip</dimen>
+    <dimen name="white_note_height">70.0dip</dimen>
+    <dimen name="white_note_padding_bottom">24.0dip</dimen>
+    <dimen name="no_results_width">260.0dip</dimen>
+    <dimen name="no_results_text_padding_bottom">16.0dip</dimen>
+    <dimen name="no_results_text_height">54.0dip</dimen>
+    <dimen name="search_text_main">20.0sp</dimen>
+    <dimen name="search_text_secondary">14.0sp</dimen>
 </resources>
index 0e00913..ec7e719 100644 (file)
     <string name="no_effects_for_you">The equalizer could not be opened.</string>
     <string name="empty_music">To copy music from your computer to your device, use a USB cable.</string>
     <string name="empty_last_added">Songs you have added over the last month will be shown here.</string>
-    <string name="empty_search">No search results found</string>
+    <string name="empty_search">NO SEARCH RESULTS FOR</string>
+    <string name="empty_search_check">Please check that you have the correct spelling or try a different keyword.</string>
     <string name="empty_favorite">Songs you mark as favorites will be shown here.</string>
     <string name="empty_recent">Albums you have listened to will show up here. Try playing some music.</string>
 
index 65e1f41..3e1ec0f 100644 (file)
         <item name="android:listViewStyle">@style/ListView</item>
         <item name="android:actionBarStyle">@style/ActionBar</item>
         <item name="android:actionOverflowButtonStyle">@style/ActionOverFlowButton</item>
-        <!-- this guy is too large - need to get a smaller one -->
-        <!--item name="android:homeAsUpIndicator">@drawable/back_arrow</item-->
+        <item name="android:actionBarWidgetTheme">@style/ActionBarWidgetTheme</item>
+        <item name="android:homeAsUpIndicator">@drawable/ic_action_back</item>
     </style>
 
-    <!-- Make the action bar not take up space - I will remove action bar this upcoming week -->
+    <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>
+        <item name="android:popupMenuStyle">@style/PopupMenu</item>
+        <item name="android:dropDownListViewStyle">@style/DropDownListView</item>
+        <item name="android:textAppearanceSmallPopupMenu">@style/SmallPopupMenu</item>
+        <item name="android:textAppearanceLargePopupMenu">@style/LargePopupMenu</item>
+    </style>
+
+    <!-- Make the action bar not take up space -->
     <style name="Eleven.Theme.ActionBar.Overlay" parent="@style/Eleven.Theme">
         <item name="android:windowActionBarOverlay">true</item>
     </style>
@@ -39,7 +48,7 @@
         <item name="android:divider">@color/list_item_divider_color</item>
         <item name="android:dividerHeight">1dp</item>
     </style>
-
+    
     <!-- Sets up the pop up menu backgroudn resource -->
     <style name="PopupMenu" parent="@android:style/Widget.Holo.PopupMenu">
         <item name="android:popupBackground">@drawable/menu_background</item>
index 6303188..c283dbb 100644 (file)
@@ -20,6 +20,7 @@ import android.view.ViewGroup;
 
 import com.cyngn.eleven.MusicPlaybackService;
 import com.cyngn.eleven.R;
+import com.cyngn.eleven.model.AlbumArtistDetails;
 import com.cyngn.eleven.utils.ApolloUtils;
 import com.cyngn.eleven.utils.MusicUtils;
 import com.cyngn.eleven.widgets.SquareImageView;
@@ -232,16 +233,6 @@ public class AlbumArtPagerAdapter extends FragmentStatePagerAdapter {
     }
 
     /**
-     * Simple container for the album and artist name as well as album id
-     */
-    private static class AlbumArtistDetails {
-        public long mAudioId;
-        public long mAlbumId;
-        public String mAlbumName;
-        public String mArtistName;
-    }
-
-    /**
      * This looks up the album and artist details for a track
      */
     private static class AlbumArtistLoader extends AsyncTask<Long, Void, AlbumArtistDetails> {
@@ -256,35 +247,7 @@ public class AlbumArtPagerAdapter extends FragmentStatePagerAdapter {
         @Override
         protected AlbumArtistDetails doInBackground(final Long... params) {
             long id = params[0];
-
-            final StringBuilder selection = new StringBuilder();
-            selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1");
-            selection.append(" AND " + BaseColumns._ID + " = '" + id + "'");
-
-            Cursor cursor = mContext.getContentResolver().query(
-                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
-                    new String[] {
-                            /* 0 */
-                            MediaStore.Audio.AudioColumns.ALBUM_ID,
-                            /* 1 */
-                            MediaStore.Audio.AudioColumns.ALBUM,
-                            /* 2 */
-                            MediaStore.Audio.AlbumColumns.ARTIST,
-                    }, selection.toString(), null, null);
-
-            if (!cursor.moveToFirst()) {
-                cursor.close();
-                return null;
-            }
-
-            AlbumArtistDetails result = new AlbumArtistDetails();
-            result.mAudioId = id;
-            result.mAlbumId = cursor.getLong(0);
-            result.mAlbumName = cursor.getString(1);
-            result.mArtistName = cursor.getString(2);
-            cursor.close();
-
-            return result;
+            return MusicUtils.getAlbumArtDetails(mContext, id);
         }
 
         @Override
diff --git a/src/com/cyngn/eleven/adapters/SummarySearchAdapter.java b/src/com/cyngn/eleven/adapters/SummarySearchAdapter.java
new file mode 100644 (file)
index 0000000..1e65739
--- /dev/null
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.adapters;
+
+import android.app.Activity;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.cache.ImageFetcher;
+import com.cyngn.eleven.format.PrefixHighlighter;
+import com.cyngn.eleven.model.SearchResult;
+import com.cyngn.eleven.sectionadapter.SectionAdapter;
+import com.cyngn.eleven.ui.MusicHolder;
+import com.cyngn.eleven.utils.ApolloUtils;
+import com.cyngn.eleven.utils.MusicUtils;
+
+import java.util.Locale;
+
+/**
+ * Used to populate the list view with the search results.
+ */
+public final class SummarySearchAdapter extends ArrayAdapter<SearchResult> implements SectionAdapter.BasicAdapter {
+
+    /**
+     * Number of views (ImageView and TextView)
+     */
+    private static final int VIEW_TYPE_COUNT = 2;
+
+    /**
+     * Image cache and image fetcher
+     */
+    private final ImageFetcher mImageFetcher;
+
+    /**
+     * Highlights the query
+     */
+    private final PrefixHighlighter mHighlighter;
+
+    /**
+     * The prefix that's highlighted
+     */
+    private char[] mPrefix;
+
+    /**
+     * Combines two strings
+     */
+    private String mCombineString;
+
+    /**
+     * Constructor for <code>SearchAdapter</code>
+     *
+     * @param context The {@link Activity} to use.
+     */
+    public SummarySearchAdapter(final Activity context) {
+        super(context, 0);
+        // Initialize the cache & image fetcher
+        mImageFetcher = ApolloUtils.getImageFetcher(context);
+        // Create the prefix highlighter
+        mHighlighter = new PrefixHighlighter(context);
+
+        mCombineString = getContext().getResources().getString(R.string.combine_two_strings);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public View getView(final int position, View convertView, final ViewGroup parent) {
+            /* Recycle ViewHolder's items */
+        MusicHolder holder;
+
+        if (convertView == null) {
+            convertView = LayoutInflater.from(getContext()).inflate(
+                    getViewResourceId(position), parent, false);
+            holder = new MusicHolder(convertView);
+            convertView.setTag(holder);
+        } else {
+            holder = (MusicHolder)convertView.getTag();
+        }
+
+        final SearchResult item = getItem(position);
+
+        switch (item.mType) {
+            case Artist:
+                // Asynchronously load the artist image into the adapter
+                mImageFetcher.loadArtistImage(item.mArtist, holder.mImage.get());
+
+                mHighlighter.setText(holder.mLineOne.get(), item.mArtist, mPrefix);
+
+                String songCount = MusicUtils.makeLabel(getContext(), R.plurals.Nsongs, item.mSongCount);
+                String albumCount = MusicUtils.makeLabel(getContext(), R.plurals.Nalbums, item.mAlbumCount);
+                holder.mLineTwo.get().setText(String.format(mCombineString, songCount, albumCount));
+
+                break;
+            case Album:
+                // Asynchronously load the album images into the adapter
+                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);
+                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(),
+                        String.format(mCombineString, item.mArtist, item.mAlbum), mPrefix);
+                break;
+            case Playlist:
+                mHighlighter.setText(holder.mLineOne.get(), item.mTitle, mPrefix);
+                String songs = MusicUtils.makeLabel(getContext(), R.plurals.Nsongs, item.mSongCount);
+                holder.mLineTwo.get().setText(songs);
+                holder.mLineThree.get().setVisibility(View.GONE);
+                break;
+        }
+
+        return convertView;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean hasStableIds() {
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getViewTypeCount() {
+        return VIEW_TYPE_COUNT;
+    }
+
+    /**
+     * This categorizes the view types we want for each item type
+     * @param position of the item
+     * @return categorization
+     */
+    @Override
+    public int getItemViewType(int position) {
+        switch (getItem(position).mType) {
+            case Artist:
+            case Album:
+            case Song:
+                return 0;
+            default:
+            case Playlist:
+                return 1;
+        }
+    }
+
+    /**
+     * this returns the layout needed for the item
+     * @param position of the item
+     * @return layout id
+     */
+    public int getViewResourceId(int position) {
+        switch (getItemViewType(position)) {
+            case 0:
+                return R.layout.list_item_normal;
+            default:
+                return R.layout.list_item_simple;
+        }
+    }
+
+    /**
+     * @param pause True to temporarily pause the disk cache, false
+     *            otherwise.
+     */
+    public void setPauseDiskCache(final boolean pause) {
+        if (mImageFetcher != null) {
+            mImageFetcher.setPauseDiskCache(pause);
+        }
+    }
+
+    /**
+     * @param prefix The query to filter.
+     */
+    public void setPrefix(final CharSequence prefix) {
+        if (!TextUtils.isEmpty(prefix)) {
+            mPrefix = prefix.toString().toUpperCase(Locale.getDefault()).toCharArray();
+        } else {
+            mPrefix = null;
+        }
+    }
+
+    @Override
+    public void unload() {
+        clear();
+    }
+
+    @Override
+    public void buildCache() {
+
+    }
+
+    @Override
+    public void flush() {
+        mImageFetcher.flush();
+    }
+
+    /**
+     * Gets the item position for a given id
+     * @param id identifies the object
+     * @return the position if found, -1 otherwise
+     */
+    @Override
+    public int getItemPosition(long id) {
+        for (int i = 0; i < getCount(); i++) {
+            if (getItem(i).mId == id) {
+                return i;
+            }
+        }
+
+        return  -1;
+    }
+}
\ No newline at end of file
index b4d82ec..e1367f2 100644 (file)
@@ -59,7 +59,12 @@ public class PrefixHighlighter {
      * @param prefix the prefix to look for
      */
     public CharSequence apply(final CharSequence text, final char[] prefix) {
-        final int mIndex = indexOfWordPrefix(text, prefix);
+        int mIndex = indexOfPrefix(text, prefix, true);
+        // prefer word prefix, if not search through the entire word
+        if (mIndex == -1) {
+            mIndex = indexOfPrefix(text, prefix, false);
+        }
+
         if (mIndex != -1) {
             if (mPrefixColorSpan == null) {
                 mPrefixColorSpan = new ForegroundColorSpan(mPrefixHighlightColor);
@@ -73,13 +78,14 @@ public class PrefixHighlighter {
     }
 
     /**
-     * Finds the index of the first word that starts with the given prefix. If
+     * Finds the index of the first character that starts with the given prefix. If
      * not found, returns -1.
      * 
      * @param text the text in which to search for the prefix
      * @param prefix the text to find, in upper case letters
+     * @param wordOnly only search for word prefixes if true
      */
-    private int indexOfWordPrefix(final CharSequence text, final char[] prefix) {
+    private int indexOfPrefix(final CharSequence text, final char[] prefix, boolean wordOnly) {
         if (TextUtils.isEmpty(text) || prefix == null) {
             return -1;
         }
@@ -113,12 +119,15 @@ public class PrefixHighlighter {
                 return i;
             }
 
-            /* Skip this word */
-            while (i < mTextLength && Character.isLetterOrDigit(text.charAt(i))) {
+            if (wordOnly) {
+                /* Skip this word */
+                while (i < mTextLength && Character.isLetterOrDigit(text.charAt(i))) {
+                    i++;
+                }
+            } else {
                 i++;
             }
         }
         return -1;
     }
-
 }
index 938b12d..91174c2 100644 (file)
@@ -72,7 +72,7 @@ public class PlaylistLoader extends WrappedAsyncTaskLoader<List<Playlist>> {
                 // Copy the playlist name
                 final String name = mCursor.getString(1);
 
-                final int songCount = getSongCount(getContext(), id);
+                final int songCount = MusicUtils.getSongCountForPlaylist(getContext(), id);
 
                 // Create a new playlist
                 final Playlist playlist = new Playlist(id, name, songCount);
@@ -114,25 +114,4 @@ public class PlaylistLoader extends WrappedAsyncTaskLoader<List<Playlist>> {
                         PlaylistsColumns.NAME
                 }, null, null, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER);
     }
-
-    /**
-     * Gets the number of songs for a playlist
-     * @param context The {@link Context} to use.
-     * @param playlistId the id of the playlist
-     * @return the # of songs in the playlist or -1 if not found
-     */
-    public static final int getSongCount(final Context context, final long playlistId) {
-        Cursor c = context.getContentResolver().query(
-                MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
-                new String[]{ BaseColumns._ID }, MusicUtils.MUSIC_ONLY_SELECTION, null, null);
-
-        if (c != null && c.moveToFirst()) {
-            int count = c.getCount();
-            c.close();
-            c = null;
-            return count;
-        }
-
-        return -1;
-    }
 }
diff --git a/src/com/cyngn/eleven/model/AlbumArtistDetails.java b/src/com/cyngn/eleven/model/AlbumArtistDetails.java
new file mode 100644 (file)
index 0000000..ea54cda
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.model;
+
+/**
+ * Simple containing class to query for album art
+ */
+public class AlbumArtistDetails {
+    public long mAudioId;
+    public long mAlbumId;
+    public String mAlbumName;
+    public String mArtistName;
+}
\ No newline at end of file
diff --git a/src/com/cyngn/eleven/model/SearchResult.java b/src/com/cyngn/eleven/model/SearchResult.java
new file mode 100644 (file)
index 0000000..b86ff07
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 Cyanogen, Inc.
+ */
+package com.cyngn.eleven.model;
+
+import android.database.Cursor;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import com.cyngn.eleven.utils.MusicUtils;
+
+import java.util.Comparator;
+
+public class SearchResult {
+    private static final String TAG = SearchResult.class.getSimpleName();
+
+    public static final Comparator COMPARATOR = new Comparator<SearchResult>() {
+        @Override
+        public int compare(final SearchResult lhs, final SearchResult rhs) {
+            return lhs.mType.ordinal() - rhs.mType.ordinal();
+        }
+    };
+
+    public static enum ResultType {
+        Song,
+        Artist,
+        Album,
+        Playlist,
+        Unknown;
+
+        public static int getNumTypes() {
+            // # of items minus the unknown
+            return ResultType.values().length - 1;
+        }
+
+        public static ResultType getResultType(final String mimetype) {
+            if (mimetype != null) {
+                if (mimetype.equals("artist")) {
+                    return Artist;
+                } else if (mimetype.equals("album")) {
+                    return Album;
+                } else if (mimetype.startsWith("audio/") || mimetype.equals("application/ogg")
+                        || mimetype.equals("application/x-ogg")) {
+                    return Song;
+                }
+            }
+
+            return Unknown;
+        }
+
+        public static ResultType getResultType(final Cursor cursor, int index) {
+            return getResultType(cursor.getString(index));
+        }
+
+        public static ResultType getResultType(final Cursor cursor) {
+            try {
+                return getResultType(cursor,
+                        cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
+            } catch(IllegalArgumentException ex) {
+                return Unknown;
+            }
+        }
+    };
+
+    public ResultType mType;
+    public String mArtist;
+    public String mAlbum;
+    public String mTitle;
+    public long mId;
+    public long mAlbumId;
+    public int mAlbumCount;
+    public int mSongCount;
+
+    public static SearchResult createSearchResult(final Cursor cursor) {
+        SearchResult result = new SearchResult();
+
+        result.mType = ResultType.getResultType(cursor);
+
+        // not a valid mime type - quitting
+        if (result.mType == ResultType.Unknown) {
+            Log.e(TAG, "No valid mime type found!");
+            return null;
+        }
+
+        // Get the Id of the content
+        result.mId = cursor.getLong(cursor
+                .getColumnIndexOrThrow(android.provider.BaseColumns._ID));
+
+        // title
+        result.mTitle = cursor.getString(cursor
+                .getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
+
+        // Get the artist name
+        result.mArtist = cursor.getString(cursor
+                .getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
+
+        // Get the album name
+        result.mAlbum = cursor.getString(cursor
+                .getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
+
+        // album count
+        result.mAlbumCount = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
+
+        // song count
+        result.mSongCount = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
+
+        return result;
+    }
+
+    public static SearchResult createPlaylistResult(final Cursor cursor) {
+        SearchResult result = new SearchResult();
+
+        result.mType = ResultType.Playlist;
+
+        // Get the Id of the content
+        result.mId = cursor.getLong(cursor
+                .getColumnIndexOrThrow(android.provider.BaseColumns._ID));
+
+        // title
+        result.mTitle = cursor.getString(cursor
+                .getColumnIndexOrThrow(MediaStore.Audio.PlaylistsColumns.NAME));
+
+        return result;
+    }
+}
\ No newline at end of file
index 17d461b..a029c07 100644 (file)
@@ -148,7 +148,7 @@ public class SectionAdapter<TItem,
             return getViewTypeCount() - 1;
         }
 
-        return mUnderlyingAdapter.getItemViewType(position);
+        return mUnderlyingAdapter.getItemViewType(getInternalPosition(position));
     }
 
     /**
@@ -286,6 +286,11 @@ public class SectionAdapter<TItem,
         notifyDataSetChanged();
     }
 
+    public void clear() {
+        mUnderlyingAdapter.clear();
+        mSectionHeaders.clear();
+    }
+
     /**
      * Gets the item position for the given identifier
      * @param identifier used to identify the object
index 21bfce1..e64e139 100644 (file)
@@ -58,7 +58,7 @@ public class SectionCreator<T> extends WrappedAsyncTaskLoader<SectionListContain
      * {@inheritDoc}
      */
     @Override
-    public SectionListContainer loadInBackground() {
+    public SectionListContainer<T> loadInBackground() {
         List<T> results = mLoader.loadInBackground();
         TreeMap<Integer, String> sectionHeaders = null;
 
@@ -66,6 +66,6 @@ public class SectionCreator<T> extends WrappedAsyncTaskLoader<SectionListContain
             sectionHeaders = SectionCreatorUtils.createSections(results, mComparator);
         }
 
-        return new SectionListContainer(sectionHeaders, results);
+        return new SectionListContainer<T>(sectionHeaders, results);
     }
 }
index 6b2b1f3..a39e2c7 100644 (file)
@@ -159,31 +159,10 @@ public abstract class BaseActivity extends FragmentActivity implements ServiceCo
     @Override
     public boolean onCreateOptionsMenu(final Menu menu) {
         // Search view
-        getMenuInflater().inflate(R.menu.search, menu);
+        getMenuInflater().inflate(R.menu.search_btn, menu);
         // Settings
         getMenuInflater().inflate(R.menu.activity_base, menu);
 
-        final SearchView searchView = (SearchView)menu.findItem(R.id.menu_search).getActionView();
-        // Add voice search
-        final SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
-        final SearchableInfo searchableInfo = searchManager.getSearchableInfo(getComponentName());
-        searchView.setSearchableInfo(searchableInfo);
-        // Perform the search
-        searchView.setOnQueryTextListener(new OnQueryTextListener() {
-
-            @Override
-            public boolean onQueryTextSubmit(final String query) {
-                // Open the search activity
-                NavUtils.openSearch(BaseActivity.this, query);
-                return true;
-            }
-
-            @Override
-            public boolean onQueryTextChange(final String newText) {
-                // Nothing to do
-                return false;
-            }
-        });
         return super.onCreateOptionsMenu(menu);
     }
 
@@ -198,6 +177,10 @@ public abstract class BaseActivity extends FragmentActivity implements ServiceCo
                 NavUtils.openSettings(this);
                 return true;
 
+            case R.id.menu_search:
+                NavUtils.openSearch(BaseActivity.this, "");
+                break;
+
             default:
                 break;
         }
index 3cb0aa2..8d570dd 100644 (file)
@@ -151,7 +151,8 @@ public class ProfileActivity extends SlidingPanelActivity implements OnPageChang
         });
         // Set up the action bar
         final ActionBar actionBar = getActionBar();
-        actionBar.setDisplayHomeAsUpEnabled(true);
+        actionBar.setIcon(R.drawable.ic_action_back);
+        actionBar.setHomeButtonEnabled(true);
 
         /* Set up the artist profile */
         if (isArtist()) {
index 810fc0b..7fefb3c 100644 (file)
 
 package com.cyngn.eleven.ui.activities;
 
-import static com.cyngn.eleven.utils.MusicUtils.mService;
-
-import android.app.Activity;
 import android.app.ActionBar;
-import android.app.LoaderManager.LoaderCallbacks;
 import android.app.SearchManager;
 import android.app.SearchableInfo;
 import android.content.ComponentName;
-import android.content.CursorLoader;
 import android.content.Context;
-import android.content.Intent;
-import android.content.Loader;
 import android.content.ServiceConnection;
 import android.database.Cursor;
 import android.media.AudioManager;
-import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.provider.BaseColumns;
 import android.provider.MediaStore;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
 import android.text.TextUtils;
 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.CursorAdapter;
-import android.widget.FrameLayout;
-import android.widget.GridView;
-import android.widget.ImageView.ScaleType;
+import android.widget.ListView;
 import android.widget.SearchView;
 import android.widget.SearchView.OnQueryTextListener;
 import android.widget.TextView;
 
 import com.cyngn.eleven.IElevenService;
 import com.cyngn.eleven.R;
-import com.cyngn.eleven.cache.ImageFetcher;
-import com.cyngn.eleven.format.PrefixHighlighter;
+import com.cyngn.eleven.adapters.SummarySearchAdapter;
+import com.cyngn.eleven.model.AlbumArtistDetails;
+import com.cyngn.eleven.model.SearchResult;
+import com.cyngn.eleven.model.SearchResult.ResultType;
 import com.cyngn.eleven.recycler.RecycleHolder;
-import com.cyngn.eleven.ui.MusicHolder;
+import com.cyngn.eleven.sectionadapter.SectionAdapter;
+import com.cyngn.eleven.sectionadapter.SectionCreator;
+import com.cyngn.eleven.sectionadapter.SectionCreator.SimpleListLoader;
+import com.cyngn.eleven.sectionadapter.SectionListContainer;
 import com.cyngn.eleven.utils.ApolloUtils;
 import com.cyngn.eleven.utils.MusicUtils;
 import com.cyngn.eleven.utils.MusicUtils.ServiceToken;
 import com.cyngn.eleven.utils.NavUtils;
+import com.cyngn.eleven.utils.SectionCreatorUtils;
+import com.cyngn.eleven.utils.SectionCreatorUtils.IItemCompare;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
-import java.util.Locale;
+import static android.view.View.OnTouchListener;
+import static com.cyngn.eleven.utils.MusicUtils.mService;
 
 /**
  * Provides the search interface for Apollo.
  * 
  * @author Andrew Neal (andrewdneal@gmail.com)
  */
-public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
-        OnScrollListener, OnQueryTextListener, OnItemClickListener, ServiceConnection {
-    /**
-     * Grid view column count. ONE - list, TWO - normal grid
-     */
-    private static final int ONE = 1, TWO = 2;
-
+public class SearchActivity extends FragmentActivity implements LoaderCallbacks<SectionListContainer<SearchResult>>,
+        OnScrollListener, OnQueryTextListener, OnItemClickListener, ServiceConnection,
+        OnTouchListener {
     /**
      * The service token
      */
@@ -85,24 +85,35 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
     private String mFilterString;
 
     /**
-     * Grid view
+     * List view
      */
-    private GridView mGridView;
+    private ListView mListView;
 
     /**
-     * List view adapter
+     * Used the filter the user's music
      */
-    private SearchAdapter mAdapter;
-
-    // Used the filter the user's music
     private SearchView mSearchView;
 
     /**
+     * IME manager
+     */
+    private InputMethodManager mImm;
+
+    /**
+     * The view that container the no search results text
+     */
+    private View mNoSearchResultsView;
+
+    /**
+     * List view adapter
+     */
+    private SectionAdapter<SearchResult, SummarySearchAdapter> mAdapter;
+
+    /**
      * {@inheritDoc}
      */
-    @SuppressWarnings("deprecation")
     @Override
-    protected void onCreate(final Bundle savedInstanceState) {
+    public void onCreate(final Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
         // Fade it in
@@ -111,59 +122,69 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
         // Control the media volume
         setVolumeControlStream(AudioManager.STREAM_MUSIC);
 
-        // Bind Apollo's service
-        mToken = MusicUtils.bindToService(this, this);
-
         // Theme the action bar
         final ActionBar actionBar = getActionBar();
-        actionBar.setTitle(getString(R.string.app_name));
-        actionBar.setDisplayHomeAsUpEnabled(true);
+        actionBar.setTitle(getString(R.string.app_name_uppercase));
+        actionBar.setHomeButtonEnabled(true);
 
+        // Bind Apollo's service
+        mToken = MusicUtils.bindToService(this, this);
 
         // Set the layout
-        setContentView(R.layout.grid_base);
+        setContentView(R.layout.search_layout);
 
-        // Get the query
-        final String query = getIntent().getStringExtra(SearchManager.QUERY);
-        mFilterString = !TextUtils.isEmpty(query) ? query : null;
+        // get the input method manager
+        mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
 
-        // Action bar subtitle
-        getActionBar().setSubtitle("\"" + mFilterString + "\"");
+        // Get the query String
+        mFilterString = getIntent().getStringExtra(SearchManager.QUERY);
 
         // Initialize the adapter
-        mAdapter = new SearchAdapter(this);
+        SummarySearchAdapter adapter = new SummarySearchAdapter(this);
+        mAdapter = new SectionAdapter<SearchResult, SummarySearchAdapter>(this, adapter);
         // Set the prefix
-        mAdapter.setPrefix(mFilterString);
-        // Initialze the list
-        mGridView = (GridView)findViewById(R.id.grid_base);
-        // Bind the data
-        mGridView.setAdapter(mAdapter);
-        // Recycle the data
-        mGridView.setRecyclerListener(new RecycleHolder());
-        // Seepd up scrolling
-        mGridView.setOnScrollListener(this);
-        mGridView.setOnItemClickListener(this);
-        if (ApolloUtils.isLandscape(this)) {
-            mGridView.setNumColumns(TWO);
-        } else {
-            mGridView.setNumColumns(ONE);
+        mAdapter.getUnderlyingAdapter().setPrefix(mFilterString);
+
+        initListView();
+
+        mNoSearchResultsView = findViewById(R.id.no_search_results);
+        mNoSearchResultsView.setVisibility(View.INVISIBLE);
+
+        if (mFilterString != null && !mFilterString.isEmpty()) {
+            // 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 + "\"");
         }
-        // Prepare the loader. Either re-connect with an existing one,
-        // or start a new one.
-        getLoaderManager().initLoader(0, null, this);
+    }
+
+    /**
+     * Sets up the list view
+     */
+    private void initListView() {
+        // Initialize the grid
+        mListView = (ListView)findViewById(R.id.list_base);
+        // Set the data behind the list
+        mListView.setAdapter(mAdapter);
+        // Release any references to the recycled Views
+        mListView.setRecyclerListener(new RecycleHolder());
+        // Show the albums and songs from the selected artist
+        mListView.setOnItemClickListener(this);
+        // To help make scrolling smooth
+        mListView.setOnScrollListener(this);
+        mListView.setOnTouchListener(this);
     }
 
     /**
      * {@inheritDoc}
      */
     @Override
-    protected void onNewIntent(final Intent intent) {
-        super.onNewIntent(intent);
-        final String query = intent.getStringExtra(SearchManager.QUERY);
-        mFilterString = !TextUtils.isEmpty(query) ? query : null;
-        // Set the prefix
-        mAdapter.setPrefix(mFilterString);
-        getLoaderManager().restartLoader(0, null, this);
+    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),
+                comparator);
     }
 
     /**
@@ -175,8 +196,25 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
         getMenuInflater().inflate(R.menu.search, menu);
 
         // Filter the list the user is looking it via SearchView
-        mSearchView = (SearchView)menu.findItem(R.id.menu_search).getActionView();
+        MenuItem searchItem = menu.findItem(R.id.menu_search);
+        mSearchView = (SearchView)searchItem.getActionView();
         mSearchView.setOnQueryTextListener(this);
+        searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
+            @Override
+            public boolean onMenuItemActionExpand(MenuItem item) {
+                return true;
+            }
+
+            @Override
+            public boolean onMenuItemActionCollapse(MenuItem item) {
+                finish();
+                return true;
+            }
+        });
+
+        if (mFilterString == null || mFilterString.isEmpty()) {
+            menu.findItem(R.id.menu_search).expandActionView();
+        }
 
         // Add voice search
         final SearchManager searchManager = (SearchManager)getSystemService(Context.SEARCH_SERVICE);
@@ -235,42 +273,37 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
      * {@inheritDoc}
      */
     @Override
-    public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
-        final Uri uri = Uri.parse("content://media/external/audio/search/fancy/"
-                + Uri.encode(mFilterString));
-        final String[] projection = new String[] {
-                BaseColumns._ID, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Artists.ARTIST,
-                MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Media.TITLE, "data1", "data2"
-        };
-        return new CursorLoader(this, uri, projection, null, null, null);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) {
-        if (data == null || data.isClosed() || data.getCount() <= 0) {
+    public void onLoadFinished(final Loader<SectionListContainer<SearchResult>> loader,
+                               final SectionListContainer<SearchResult> data) {
+        // Check for any errors
+        if (data.mListResults.isEmpty()) {
             // Set the empty text
-            final TextView empty = (TextView)findViewById(R.id.empty);
-            empty.setText(getString(R.string.empty_search));
-            mGridView.setEmptyView(empty);
-            return;
+            final TextView empty = (TextView)findViewById(R.id.no_search_results_text);
+            empty.setText("\"" + mFilterString + "\"");
+
+            // clear the adapter
+            mAdapter.clear();
+
+            // hide listview, show no results text
+            mListView.setVisibility(View.INVISIBLE);
+            mNoSearchResultsView.setVisibility(View.VISIBLE);
+        } else {
+            // Set the data
+            mAdapter.setData(data);
+
+            // show listview, hide no results text
+            mListView.setVisibility(View.VISIBLE);
+            mNoSearchResultsView.setVisibility(View.INVISIBLE);
         }
-        // Swap the new cursor in. (The framework will take care of closing the
-        // old cursor once we return.)
-        mAdapter.swapCursor(data);
     }
 
     /**
      * {@inheritDoc}
      */
     @Override
-    public void onLoaderReset(final Loader<Cursor> loader) {
-        // This is called when the last Cursor provided to onLoadFinished()
-        // above is about to be closed. We need to make sure we are no
-        // longer using it.
-        mAdapter.swapCursor(null);
+    public void onLoaderReset(final Loader<SectionListContainer<SearchResult>> loader) {
+        // Clear the data in the adapter
+        mAdapter.unload();
     }
 
     /**
@@ -281,9 +314,9 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
         // Pause disk cache access to ensure smoother scrolling
         if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING
                 || scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
-            mAdapter.setPauseDiskCache(true);
+            mAdapter.getUnderlyingAdapter().setPauseDiskCache(true);
         } else {
-            mAdapter.setPauseDiskCache(false);
+            mAdapter.getUnderlyingAdapter().setPauseDiskCache(false);
             mAdapter.notifyDataSetChanged();
         }
     }
@@ -294,21 +327,28 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
     @Override
     public boolean onQueryTextSubmit(final String query) {
         if (TextUtils.isEmpty(query)) {
+            mFilterString = "";
+            getActionBar().setSubtitle("");
             return false;
         }
+
+        hideInputManager();
+
+        // Action bar subtitle
+        getActionBar().setSubtitle("\"" + mFilterString + "\"");
+        return true;
+    }
+
+    public void hideInputManager() {
         // When the search is "committed" by the user, then hide the keyboard so
         // the user can
         // more easily browse the list of results.
         if (mSearchView != null) {
-            final InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
-            if (imm != null) {
-                imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
+            if (mImm != null && mImm.isImeShowing()) {
+                mImm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
             }
             mSearchView.clearFocus();
         }
-        // Action bar subtitle
-        getActionBar().setSubtitle("\"" + mFilterString + "\"");
-        return true;
     }
 
     /**
@@ -317,6 +357,9 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
     @Override
     public boolean onQueryTextChange(final String newText) {
         if (TextUtils.isEmpty(newText)) {
+            mListView.setVisibility(View.INVISIBLE);
+            mNoSearchResultsView.setVisibility(View.INVISIBLE);
+            mFilterString = "";
             return false;
         }
         // Called when the action bar search text has changed. Update
@@ -324,8 +367,8 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
         // with this filter.
         mFilterString = !TextUtils.isEmpty(newText) ? newText : null;
         // Set the prefix
-        mAdapter.setPrefix(mFilterString);
-        getLoaderManager().restartLoader(0, null, this);
+        mAdapter.getUnderlyingAdapter().setPrefix(mFilterString);
+        getSupportLoaderManager().restartLoader(0, null, this);
         return true;
     }
 
@@ -335,38 +378,25 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
     @Override
     public void onItemClick(final AdapterView<?> parent, final View view, final int position,
             final long id) {
-        Cursor cursor = mAdapter.getCursor();
-        cursor.moveToPosition(position);
-        if (cursor.isBeforeFirst() || cursor.isAfterLast()) {
-            return;
-        }
-        // Get the MIME type
-        final String mimeType = cursor.getString(cursor
-                .getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
-
-        // If it's an artist, open the artist profile
-        if ("artist".equals(mimeType)) {
-            NavUtils.openArtistProfile(this,
-                    cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST)));
-        } else if ("album".equals(mimeType)) {
-            // If it's an album, open the album profile
-            NavUtils.openAlbumProfile(this,
-                    cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM)),
-                    cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST)),
-                    cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Albums._ID)));
-        } else if (position >= 0 && id >= 0) {
-            // If it's a song, play it and leave
-            final long[] list = new long[] {
-                id
-            };
-            MusicUtils.playAll(this, list, 0, false);
+        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, null, 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;
         }
-
-        // Close it up
-        cursor.close();
-        cursor = null;
-        // All done
-        finish();
     }
 
     /**
@@ -386,180 +416,152 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
     }
 
     /**
-     * Used to populate the list view with the search results.
+     * This class loads a search result summary of items
      */
-    private static final class SearchAdapter extends CursorAdapter {
-
-        /**
-         * Number of views (ImageView and TextView)
-         */
-        private static final int VIEW_TYPE_COUNT = 2;
-
-        /**
-         * Image cache and image fetcher
-         */
-        private final ImageFetcher mImageFetcher;
-
-        /**
-         * Highlights the query
-         */
-        private final PrefixHighlighter mHighlighter;
-
-        /**
-         * The prefix that's highlighted
-         */
-        private char[] mPrefix;
-
-        /**
-         * Constructor for <code>SearchAdapter</code>
-         * 
-         * @param context The {@link Context} to use.
-         */
-        public SearchAdapter(final Activity context) {
-            super(context, null, false);
-            // Initialize the cache & image fetcher
-            mImageFetcher = ApolloUtils.getImageFetcher(context);
-            // Create the prefix highlighter
-            mHighlighter = new PrefixHighlighter(context);
+    private static final class SummarySearchLoader extends SimpleListLoader<SearchResult> {
+        private static final int NUM_RESULTS_TO_GET = 3;
+
+        private final String mQuery;
+
+        public SummarySearchLoader(final Context context, final String query) {
+            super(context);
+            mQuery = query;
         }
 
-        /**
-         * {@inheritDoc}
-         */
         @Override
-        public void bindView(final View convertView, final Context context, final Cursor cursor) {
-            /* Recycle ViewHolder's items */
-            MusicHolder holder = (MusicHolder)convertView.getTag();
-            if (holder == null) {
-                holder = new MusicHolder(convertView);
-                convertView.setTag(holder);
+        public List<SearchResult> loadInBackground() {
+            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;
+
+            // current number of results we have
+            int numResultsAdded = 0;
+
+            // count for each result type
+            int[] numOfEachType = new int[numTypes];
+
+            // search playlists first
+            Cursor playlistCursor = makePlaylistSearchCursor(getContext(), mQuery);
+            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);
+
+                // 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;
+
+                // close the cursor
+                playlistCursor.close();
+                playlistCursor = null;
             }
 
-            // Get the MIME type
-            final String mimetype = cursor.getString(cursor
-                    .getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE));
-
-            if (mimetype.equals("artist")) {
-                holder.mImage.get().setScaleType(ScaleType.CENTER_CROP);
-
-                // Get the artist name
-                final String artist = cursor.getString(cursor
-                        .getColumnIndexOrThrow(MediaStore.Audio.Artists.ARTIST));
-                holder.mLineOne.get().setText(artist);
-
-                // Get the album count
-                final int albumCount = cursor.getInt(cursor.getColumnIndexOrThrow("data1"));
-                holder.mLineTwo.get().setText(
-                        MusicUtils.makeLabel(context, R.plurals.Nalbums, albumCount));
-
-                // Get the song count
-                final int songCount = cursor.getInt(cursor.getColumnIndexOrThrow("data2"));
-                holder.mLineThree.get().setText(
-                        MusicUtils.makeLabel(context, R.plurals.Nsongs, songCount));
-
-                // Asynchronously load the artist image into the adapter
-                mImageFetcher.loadArtistImage(artist, holder.mImage.get());
-
-                // Highlght the query
-                mHighlighter.setText(holder.mLineOne.get(), artist, mPrefix);
-            } else if (mimetype.equals("album")) {
-                holder.mImage.get().setScaleType(ScaleType.FIT_XY);
-
-                // Get the Id of the album
-                final long id = cursor.getLong(cursor
-                        .getColumnIndexOrThrow(MediaStore.Audio.Albums._ID));
-
-                // Get the album name
-                final String album = cursor.getString(cursor
-                        .getColumnIndexOrThrow(MediaStore.Audio.Albums.ALBUM));
-                holder.mLineOne.get().setText(album);
-
-                // Get the artist name
-                final String artist = cursor.getString(cursor
-                        .getColumnIndexOrThrow(MediaStore.Audio.Albums.ARTIST));
-                holder.mLineTwo.get().setText(artist);
-
-                // Asynchronously load the album images into the adapter
-                mImageFetcher.loadAlbumImage(artist, album, id, holder.mImage.get());
-                // Asynchronously load the artist image into the adapter
-                mImageFetcher.loadArtistImage(artist, holder.mBackground.get());
-
-                // Highlght the query
-                mHighlighter.setText(holder.mLineOne.get(), album, mPrefix);
-
-            } else if (mimetype.startsWith("audio/") || mimetype.equals("application/ogg")
-                    || mimetype.equals("application/x-ogg")) {
-                holder.mImage.get().setScaleType(ScaleType.CENTER_CROP);
-                holder.mImage.get().setImageResource(R.drawable.default_artwork);
-
-                // Get the track name
-                final String track = cursor.getString(cursor
-                        .getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));
-                holder.mLineOne.get().setText(track);
-
-                // Get the album name
-                final String album = cursor.getString(cursor
-                        .getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM));
-                holder.mLineTwo.get().setText(album);
-
-                final String artist = cursor.getString(cursor
-                        .getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));
-                // Asynchronously load the artist image into the adapter
-                mImageFetcher.loadArtistImage(artist, holder.mBackground.get());
-                holder.mLineThree.get().setText(artist);
-
-                // Highlght the query
-                mHighlighter.setText(holder.mLineOne.get(), track, mPrefix);
+            // do fancy audio search
+            Cursor cursor = ApolloUtils.createSearchQueryCursor(getContext(), mQuery);
+
+            // pre-cache this index
+            final int mimeTypeIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.MIME_TYPE);
+
+            // walk through the cursor
+            if (cursor != null && cursor.moveToFirst()) {
+                do {
+                    // get the result type
+                    ResultType type = ResultType.getResultType(cursor, mimeTypeIndex);
+
+                    // if we still need this type
+                    if (numOfEachType[type.ordinal()] < 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;
+                                }
+                            }
+
+                            // add it
+                            results.add(item);
+                            numOfEachType[type.ordinal()]++;
+                            numResultsAdded++;
+
+                            // if we have enough then quit
+                            if (numResultsAdded >= numResultsNeeded) {
+                                break;
+                            }
+                        }
+                    }
+                } while (cursor.moveToNext());
+
+                cursor.close();
+                cursor = null;
             }
-        }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
-            return ((Activity)context).getLayoutInflater().inflate(
-                    R.layout.list_item_detailed, parent, false);
-        }
+            // sort our results
+            Collections.sort(results, SearchResult.COMPARATOR);
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public boolean hasStableIds() {
-            return true;
+            return results;
         }
 
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int getViewTypeCount() {
-            return VIEW_TYPE_COUNT;
-        }
+        public static Cursor makePlaylistSearchCursor(final Context context,
+                                                      final String searchTerms) {
+            if (searchTerms == null || searchTerms.isEmpty()) {
+                return null;
+            }
 
-        /**
-         * @param pause True to temporarily pause the disk cache, false
-         *            otherwise.
-         */
-        public void setPauseDiskCache(final boolean pause) {
-            if (mImageFetcher != null) {
-                mImageFetcher.setPauseDiskCache(pause);
+            // trim out special characters like % or \ as well as things like "a" "and" etc
+            String trimmedSearchTerms = MusicUtils.getTrimmedName(searchTerms);
+
+            if (trimmedSearchTerms == null || trimmedSearchTerms.isEmpty()) {
+                return null;
             }
-        }
 
-        /**
-         * @param prefix The query to filter.
-         */
-        public void setPrefix(final CharSequence prefix) {
-            if (!TextUtils.isEmpty(prefix)) {
-                mPrefix = prefix.toString().toUpperCase(Locale.getDefault()).toCharArray();
-            } else {
-                mPrefix = null;
+            String[] keywords = trimmedSearchTerms.split(" ");
+
+            // prep the keyword for like search
+            for (int i = 0; i < keywords.length; i++) {
+                keywords[i] = "%" + keywords[i] + "%";
+            }
+
+            String where = "";
+
+            // make the where clause
+            for (int i = 0; i < keywords.length; i++) {
+                if (i == 0) {
+                    where = "name LIKE ?";
+                } else {
+                    where += " AND name LIKE ?";
+                }
             }
+
+            return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                    new String[]{
+                        /* 0 */
+                            BaseColumns._ID,
+                        /* 1 */
+                            MediaStore.Audio.PlaylistsColumns.NAME
+                    }, where, keywords, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER);
         }
     }
 
+
+
     /**
      * {@inheritDoc}
      */
@@ -569,4 +571,9 @@ public class SearchActivity extends Activity implements LoaderCallbacks<Cursor>,
         // Nothing to do
     }
 
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        hideInputManager();
+        return false;
+    }
 }
index 3d2c739..1f8a68d 100644 (file)
@@ -64,7 +64,8 @@ public class SettingsActivity extends PreferenceActivity {
         mImageCache = ImageCache.getInstance(this);
 
         // UP
-        getActionBar().setDisplayHomeAsUpEnabled(true);
+        getActionBar().setIcon(R.drawable.ic_action_back);
+        getActionBar().setHomeButtonEnabled(true);
 
         // Add the preferences
         addPreferencesFromResource(R.xml.settings);
index 4f775cf..7d58c98 100644 (file)
@@ -47,6 +47,7 @@ import com.cyngn.eleven.recycler.RecycleHolder;
 import com.cyngn.eleven.ui.activities.BaseActivity;
 import com.cyngn.eleven.ui.activities.ProfileActivity;
 import com.cyngn.eleven.utils.MusicUtils;
+import com.cyngn.eleven.utils.NavUtils;
 
 import java.util.List;
 
@@ -222,21 +223,11 @@ public class PlaylistFragment extends Fragment implements LoaderCallbacks<List<P
         mPlaylist = mAdapter.getItem(position);
         String playlistName;
         if (position == 0) {
-            playlistName = getString(R.string.playlist_last_added);
-            bundle.putString(Config.MIME_TYPE, getString(R.string.playlist_last_added));
+            String lastAdded = getString(R.string.playlist_last_added);
+            NavUtils.openPlaylist(getActivity(), -1, lastAdded, lastAdded);
         } else {
-            // User created
-            playlistName = mPlaylist.mPlaylistName;
-            bundle.putString(Config.MIME_TYPE, MediaStore.Audio.Playlists.CONTENT_TYPE);
-            bundle.putLong(Config.ID, mPlaylist.mPlaylistId);
+            NavUtils.openPlaylist(getActivity(), mPlaylist.mPlaylistId, null, mPlaylist.mPlaylistName);
         }
-
-        bundle.putString(Config.NAME, playlistName);
-
-        // Create the intent to launch the profile activity
-        final Intent intent = new Intent(getActivity(), ProfileActivity.class);
-        intent.putExtras(bundle);
-        startActivity(intent);
     }
 
     /**
index 2da6c1a..49c228f 100644 (file)
@@ -19,14 +19,17 @@ import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
+import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
+import android.provider.BaseColumns;
 import android.provider.MediaStore;
 import android.util.Log;
 import android.util.TypedValue;
@@ -299,4 +302,22 @@ public final class ApolloUtils {
 
         return 0;
     }
+
+    /**
+     * Returns a fancy search query cursor
+     * @param context
+     * @param query query string
+     * @return cursor of the results
+     */
+    public static Cursor createSearchQueryCursor(final Context context, final String query) {
+        final Uri uri = Uri.parse("content://media/external/audio/search/fancy/"
+                + Uri.encode(query));
+        final String[] projection = new String[] {
+                BaseColumns._ID, MediaStore.Audio.Media.MIME_TYPE, MediaStore.Audio.Artists.ARTIST,
+                MediaStore.Audio.Albums.ALBUM, MediaStore.Audio.Media.TITLE, "data1", "data2"
+        };
+
+        // no selection/selection/sort args - they are ignored by fancy search anyways
+        return context.getContentResolver().query(uri, projection, null, null, null);
+    }
 }
index 5a0e6a4..d4067ba 100644 (file)
@@ -45,6 +45,7 @@ import com.cyngn.eleven.loaders.LastAddedLoader;
 import com.cyngn.eleven.loaders.PlaylistLoader;
 import com.cyngn.eleven.loaders.SongLoader;
 import com.cyngn.eleven.menu.FragmentMenuItems;
+import com.cyngn.eleven.model.AlbumArtistDetails;
 import com.cyngn.eleven.provider.RecentStore;
 import com.devspark.appmsg.AppMsg;
 
@@ -788,10 +789,10 @@ public final class MusicUtils {
      */
     public static final long getIdForArtist(final Context context, final String name) {
         Cursor cursor = context.getContentResolver().query(
-                MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[] {
-                    BaseColumns._ID
-                }, ArtistColumns.ARTIST + "=?", new String[] {
-                    name
+                MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, new String[]{
+                        BaseColumns._ID
+                }, ArtistColumns.ARTIST + "=?", new String[]{
+                        name
                 }, ArtistColumns.ARTIST);
         int id = -1;
         if (cursor != null) {
@@ -1031,6 +1032,59 @@ public final class MusicUtils {
     }
 
     /**
+     * Gets the number of songs for a playlist
+     * @param context The {@link Context} to use.
+     * @param playlistId the id of the playlist
+     * @return the # of songs in the playlist
+     */
+    public static final int getSongCountForPlaylist(final Context context, final long playlistId) {
+        Cursor c = context.getContentResolver().query(
+                MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
+                new String[]{BaseColumns._ID}, MusicUtils.MUSIC_ONLY_SELECTION, null, null);
+
+        if (c != null && c.moveToFirst()) {
+            int count = c.getCount();
+            c.close();
+            c = null;
+            return count;
+        }
+
+        return 0;
+    }
+
+    public static final AlbumArtistDetails getAlbumArtDetails(final Context context, final long trackId) {
+        final StringBuilder selection = new StringBuilder();
+        selection.append(MediaStore.Audio.AudioColumns.IS_MUSIC + "=1");
+        selection.append(" AND " + BaseColumns._ID + " = '" + trackId + "'");
+
+        Cursor cursor = context.getContentResolver().query(
+            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+            new String[] {
+                    /* 0 */
+                MediaStore.Audio.AudioColumns.ALBUM_ID,
+                    /* 1 */
+                MediaStore.Audio.AudioColumns.ALBUM,
+                    /* 2 */
+                MediaStore.Audio.AlbumColumns.ARTIST,
+            }, selection.toString(), null, null
+        );
+
+        if (!cursor.moveToFirst()) {
+            cursor.close();
+            return null;
+        }
+
+        AlbumArtistDetails result = new AlbumArtistDetails();
+        result.mAudioId = trackId;
+        result.mAlbumId = cursor.getLong(0);
+        result.mAlbumName = cursor.getString(1);
+        result.mArtistName = cursor.getString(2);
+        cursor.close();
+
+        return result;
+    }
+
+    /**
      * @param context The {@link Context} to use.
      * @param id The id of the album.
      * @return The release date for an album.
@@ -1364,6 +1418,38 @@ public final class MusicUtils {
     /**
      * A snippet is taken from MediaStore.Audio.keyFor method
      * This will take a name, removes things like "the", "an", etc
+     * as well as special characters and return it
+     * @param name the string to trim
+     * @return the trimmed name
+     */
+    public static String getTrimmedName(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+
+        name = name.trim().toLowerCase();
+        if (name.startsWith("the ")) {
+            name = name.substring(4);
+        }
+        if (name.startsWith("an ")) {
+            name = name.substring(3);
+        }
+        if (name.startsWith("a ")) {
+            name = name.substring(2);
+        }
+        if (name.endsWith(", the") || name.endsWith(",the") ||
+                name.endsWith(", an") || name.endsWith(",an") ||
+                name.endsWith(", a") || name.endsWith(",a")) {
+            name = name.substring(0, name.lastIndexOf(','));
+        }
+        name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
+
+        return name;
+    }
+
+    /**
+     * A snippet is taken from MediaStore.Audio.keyFor method
+     * This will take a name, removes things like "the", "an", etc
      * as well as special characters, then find the localized label
      * @param name Name to get the label of
      * @param trimName boolean flag to run the trimmer on the name
@@ -1375,22 +1461,7 @@ public final class MusicUtils {
         }
 
         if (trimName) {
-            name = name.trim().toLowerCase();
-            if (name.startsWith("the ")) {
-                name = name.substring(4);
-            }
-            if (name.startsWith("an ")) {
-                name = name.substring(3);
-            }
-            if (name.startsWith("a ")) {
-                name = name.substring(2);
-            }
-            if (name.endsWith(", the") || name.endsWith(",the") ||
-                    name.endsWith(", an") || name.endsWith(",an") ||
-                    name.endsWith(", a") || name.endsWith(",a")) {
-                name = name.substring(0, name.lastIndexOf(','));
-            }
-            name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
+            name = getTrimmedName(name);
         }
 
         if (name.length() > 0) {
index fad64c4..f18f861 100644 (file)
@@ -82,6 +82,31 @@ public final class NavUtils {
     }
 
     /**
+     * Opens the playlist view
+     *
+     * @param context The {@link Activity} to use.
+     * @param playlistId the id of the playlist
+     * @param mimeType the mimetype if specified, or the default if null passed in
+     * @param playlistName the playlist name
+     */
+    public static void openPlaylist(final Activity context, final long playlistId,
+                                    final String mimeType, final String playlistName) {
+        final Bundle bundle = new Bundle();
+        bundle.putLong(Config.ID, playlistId);
+        if (mimeType != null) {
+            bundle.putString(Config.MIME_TYPE, mimeType);
+        } else {
+            bundle.putString(Config.MIME_TYPE, MediaStore.Audio.Playlists.CONTENT_TYPE);
+        }
+        bundle.putString(Config.NAME, playlistName);
+
+        // Create the intent to launch the profile activity
+        final Intent intent = new Intent(context, ProfileActivity.class);
+        intent.putExtras(bundle);
+        context.startActivity(intent);
+    }
+
+    /**
      * Opens the sound effects panel or DSP manager in CM
      * 
      * @param context The {@link Activity} to use.
@@ -108,7 +133,7 @@ public final class NavUtils {
     }
 
     /**
-     * Opens to {@link SearchActivity}.
+     * Opens to {@link com.cyngn.eleven.ui.activities.SearchActivity}.
      * 
      * @param activity The {@link Activity} to use.
      * @param query The search query.
index 9d563d2..683c0f4 100644 (file)
@@ -8,6 +8,7 @@ import android.content.Context;
 import com.cyngn.eleven.R;
 import com.cyngn.eleven.model.Album;
 import com.cyngn.eleven.model.Artist;
+import com.cyngn.eleven.model.SearchResult;
 import com.cyngn.eleven.model.Song;
 
 import java.util.List;
@@ -451,4 +452,39 @@ public class SectionCreatorUtils {
 
         return sectionCreator;
     }
+
+    /**
+     * Returns an song comparison based on the current sort
+     * @param context Context for string generation
+     * @return the song comparison method
+     */
+    public static IItemCompare<SearchResult> createSearchResultComparison(final Context context) {
+        return new IItemCompare<SearchResult>() {
+
+            @Override
+            public String createSectionSeparator(SearchResult first, SearchResult second) {
+                if (first == null || first.mType != second.mType) {
+                    return createLabel(second);
+                }
+
+                return null;
+            }
+
+            @Override
+            public String createLabel(SearchResult item) {
+                switch (item.mType) {
+                    case Artist:
+                        return context.getString(R.string.page_artists);
+                    case Album:
+                        return context.getString(R.string.page_albums);
+                    case Song:
+                        return context.getString(R.string.page_songs);
+                    case Playlist:
+                        return context.getString(R.string.page_playlists);
+                }
+
+                return null;
+            }
+        };
+    }
 }