LOCAL_SRC_FILES += $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-v4 \
android-support-v8-renderscript \
android-common \
+ eleven_support_v4 \
eleven_nineoldandroids \
- eleven_palette
+ eleven_palette \
+ eleven_recyclerview
LOCAL_PACKAGE_NAME := Eleven
LOCAL_OVERRIDES_PACKAGES := Music
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
eleven_nineoldandroids:libs/nineoldandroids-2.4.0.jar \
- eleven_palette:libs/android-support-v7-palette.jar
+ eleven_palette:libs/android-support-v7-palette.jar \
+ eleven_recyclerview:libs/android-support-v7-recyclerview.jar \
+ eleven_support_v4:libs/android-support-v4-21.jar
include $(BUILD_MULTI_PREBUILT)
<!-- ICS to Jelly Bean -->
<uses-sdk
android:minSdkVersion="16"
- android:targetSdkVersion="18" />
+ android:targetSdkVersion="20" />
<!-- Used for caching and creating new playlists -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
android:screenOrientation="portrait"
android:theme="@style/Eleven.Theme.ActionBar.Overlay">
</activity>
+ <!-- Artist Detail Activity -->
+ <activity
+ android:name=".ui.activities.ArtistDetailActivity"
+ android:excludeFromRecents="true"
+ android:screenOrientation="portrait"
+ android:theme="@style/Eleven.Theme.ActionBar.Overlay">
+ </activity>
<!-- Shortcut launcher Activity -->
<activity
android:name=".ui.activities.ShortcutActivity"
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/background_color" >
+
+ <ListView
+ android:id="@+id/songs"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:scrollbars="none"
+ android:divider="@color/background_color" />
+</RelativeLayout>
\ No newline at end of file
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="120dp"
+ android:layout_height="161dp"
+ android:layout_marginRight="4dp"
+ android:background="@drawable/cardcontainer" >
+
+ <ImageView
+ android:id="@+id/album_art"
+ android:layout_width="120dp"
+ android:layout_height="120dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true" />
+
+ <!-- FIXME: hide this until we know what goes in the popup menu -->
+ <ImageView
+ android:visibility="gone"
+ android:id="@+id/overflow"
+ android:layout_width="24dp"
+ android:layout_height="40dp"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:layout_marginBottom="1dp"
+ android:layout_marginRight="-2dp"
+ android:gravity="center"
+ android:src="@drawable/menu_item_button" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/album_art"
+ android:layout_toLeftOf="@id/overflow"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="8dp"
+ android:layout_marginTop="3dp"
+ android:singleLine="true"
+ android:fontFamily="sans-serif"
+ android:textStyle="bold"
+ android:textSize="@dimen/text_size_small"
+ android:textColor="@color/default_text_color" />
+
+ <TextView
+ android:id="@+id/year"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_alignParentLeft="true"
+ android:layout_toLeftOf="@id/overflow"
+ android:layout_marginLeft="8dp"
+ android:layout_marginTop="-4dp"
+ android:singleLine="true"
+ android:fontFamily="sans-serif-light"
+ android:textSize="@dimen/text_size_micro"
+ android:textColor="@color/default_text_color" />
+</RelativeLayout>
\ No newline at end of file
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/background_color" >
+
+ <ImageView
+ android:id="@+id/hero"
+ android:layout_width="match_parent"
+ android:layout_height="220dp"
+ android:scaleType="centerCrop" />
+
+ <TextView
+ android:id="@+id/albums_label"
+ android:layout_width="wrap_content"
+ android:layout_height="40dp"
+ android:layout_marginLeft="10dp"
+ android:layout_below="@id/hero"
+ android:gravity="center_vertical"
+ android:fontFamily="sans-serif-light"
+ android:textColor="@color/list_item_header_text_color"
+ android:textSize="@dimen/text_size_medium"
+ android:text="@string/header_albums" />
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/albums"
+ android:scrollbars="none"
+ android:layout_width="match_parent"
+ android:layout_height="161dp"
+ android:layout_below="@id/albums_label" />
+
+ <TextView
+ android:id="@+id/songs_label"
+ android:layout_width="wrap_content"
+ android:layout_height="30dp"
+ android:layout_marginLeft="10dp"
+ android:layout_below="@id/albums"
+ android:gravity="bottom"
+ android:fontFamily="sans-serif-light"
+ android:textColor="@color/list_item_header_text_color"
+ android:textSize="@dimen/text_size_medium"
+ android:text="@string/header_all_songs" />
+
+</RelativeLayout>
\ No newline at end of file
--- /dev/null
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="70dp" >
+
+ <ImageView
+ android:id="@+id/album_art"
+ android:layout_width="50dp"
+ android:layout_height="50dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_margin="10dp" />
+
+ <!-- FIXME: hide this until we know what goes in the popup menu -->
+ <ImageView
+ android:visibility="gone"
+ android:id="@+id/overflow"
+ android:layout_width="24dp"
+ android:layout_height="68dp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentRight="true"
+ android:layout_marginBottom="2dp"
+ android:gravity="center"
+ android:src="@drawable/menu_item_button" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_toLeftOf="@id/overflow"
+ android:layout_toRightOf="@id/album_art"
+ android:layout_marginTop="16dp"
+ android:singleLine="true"
+ android:fontFamily="sans-serif"
+ android:textStyle="bold"
+ android:textSize="@dimen/text_size_small"
+ android:textColor="@color/default_text_color" />
+
+ <TextView
+ android:id="@+id/album"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_toRightOf="@id/album_art"
+ android:layout_toLeftOf="@id/overflow"
+ android:layout_marginTop="-2dp"
+ android:singleLine="true"
+ android:fontFamily="sans-serif-light"
+ android:textSize="@dimen/text_size_micro"
+ android:textColor="@color/default_text_color" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentLeft="true"
+ android:layout_marginLeft="10dp"
+ android:layout_marginRight="10dp"
+ android:background="@color/list_item_divider_color" />
+</RelativeLayout>
\ No newline at end of file
<string name="page_now_playing">NOW PLAYING</string>
<string name="page_play_queue">PLAY QUEUE</string>
+ <!-- Section Headers -->
+ <string name="header_albums">ALBUMS</string>
+ <string name="header_top_songs">TOP SONGS</string>
+ <string name="header_all_songs">ALL SONGS</string>
+
<!-- Option menu items -->
<string name="menu_settings">Settings</string>
<string name="menu_search">Search</string>
--- /dev/null
+package com.cyngn.eleven.adapters;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.cyngn.eleven.Config;
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.cache.ImageFetcher;
+import com.cyngn.eleven.loaders.ArtistAlbumLoader;
+import com.cyngn.eleven.model.Album;
+import com.cyngn.eleven.utils.ApolloUtils;
+import com.cyngn.eleven.utils.NavUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ArtistDetailAlbumAdapter
+extends RecyclerView.Adapter<ArtistDetailAlbumAdapter.ViewHolder>
+implements LoaderCallbacks<List<Album>> {
+ private static final int TYPE_FIRST = 1;
+ private static final int TYPE_MIDDLE = 2;
+ private static final int TYPE_LAST = 3;
+
+ private final Activity mActivity;
+ private final ImageFetcher mImageFetcher;
+ private final LayoutInflater mInflater;
+ private List<Album> mAlbums = Collections.emptyList();
+
+ public ArtistDetailAlbumAdapter(final Activity activity) {
+ mActivity = activity;
+ mImageFetcher = ApolloUtils.getImageFetcher(activity);
+ mInflater = LayoutInflater.from(activity);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ // use view types to distinguish first and last elements
+ // so they can be given special treatment for layout
+ if(position == 0) { return TYPE_FIRST; }
+ else if(position == getItemCount()-1) { return TYPE_LAST; }
+ else return TYPE_MIDDLE;
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View v = mInflater.inflate(R.layout.artist_detail_album, parent, false);
+ // add extra margin to the first and last elements
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
+ if (viewType == TYPE_FIRST) { params.leftMargin = 30; }
+ else if(viewType == TYPE_LAST) { params.rightMargin = 30; }
+ return new ViewHolder(v);
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ Album a = mAlbums.get(position);
+ holder.title.setText(a.mAlbumName);
+ holder.year.setText(a.mYear);
+ mImageFetcher.loadAlbumImage(
+ a.mArtistName, a.mAlbumName, a.mAlbumId, holder.art);
+ addAction(holder.itemView, a);
+ }
+
+ private void addAction(View view, final Album album) {
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ NavUtils.openAlbumProfile(
+ mActivity, album.mAlbumName, album.mArtistName, album.mAlbumId);
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() { return mAlbums.size(); }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public ImageView art;
+ public TextView title;
+ public TextView year;
+ public ViewHolder(View root) {
+ super(root);
+ art = (ImageView)root.findViewById(R.id.album_art);
+ title = (TextView)root.findViewById(R.id.title);
+ year = (TextView)root.findViewById(R.id.year);
+ }
+ }
+
+ @Override // LoaderCallbacks
+ public Loader<List<Album>> onCreateLoader(int id, Bundle args) {
+ return new ArtistAlbumLoader(mActivity, args.getLong(Config.ID));
+ }
+
+ @Override // LoaderCallbacks
+ public void onLoadFinished(Loader<List<Album>> loader, List<Album> albums) {
+ if (albums.isEmpty()) { return; }
+ mAlbums = albums;
+ notifyDataSetChanged();
+ }
+
+ @Override // LoaderCallbacks
+ public void onLoaderReset(Loader<List<Album>> loader) {
+ mAlbums = Collections.emptyList();
+ notifyDataSetChanged();
+ mImageFetcher.flush();
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.cyngn.eleven.adapters;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.cyngn.eleven.Config;
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.cache.ImageFetcher;
+import com.cyngn.eleven.loaders.ArtistSongLoader;
+import com.cyngn.eleven.model.Song;
+import com.cyngn.eleven.utils.ApolloUtils;
+import com.cyngn.eleven.utils.MusicUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ArtistDetailSongAdapter extends BaseAdapter
+implements LoaderCallbacks<List<Song>> {
+ private final Activity mActivity;
+ private final ImageFetcher mImageFetcher;
+ private final LayoutInflater mInflater;
+ private List<Song> mSongs = Collections.emptyList();
+
+ public ArtistDetailSongAdapter(final Activity activity) {
+ mActivity = activity;
+ mImageFetcher = ApolloUtils.getImageFetcher(activity);
+ mInflater = LayoutInflater.from(activity);
+ }
+
+ @Override
+ public int getCount() { return mSongs.size(); }
+
+ @Override
+ public Song getItem(int pos) { return mSongs.get(pos); }
+
+ @Override
+ public long getItemId(int pos) { return pos; }
+
+ @Override
+ public View getView(int pos, View convertView, ViewGroup parent) {
+ if(convertView == null) {
+ convertView = mInflater.inflate(R.layout.artist_detail_song, parent, false);
+ convertView.setTag(new Holder(convertView));
+ }
+
+ Holder h = (Holder)convertView.getTag();
+ Song s = getItem(pos);
+ h.title.setText(s.mSongName);
+ h.album.setText(s.mAlbumName);
+
+ if (s.mAlbumId >= 0) {
+ mImageFetcher.loadAlbumImage(s.mArtistName, s.mAlbumName, s.mAlbumId, h.art);
+ }
+
+ addAction(convertView, s);
+
+ return convertView;
+ }
+
+ private void addAction(View view, final Song song) {
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ MusicUtils.playAll(mActivity, new long[] { song.mSongId }, -1, false);
+ }
+ });
+ }
+
+ @Override // LoaderCallbacks
+ public Loader<List<Song>> onCreateLoader(int id, Bundle args) {
+ return new ArtistSongLoader(mActivity, args.getLong(Config.ID));
+ }
+
+ @Override // LoaderCallbacks
+ public void onLoadFinished(Loader<List<Song>> loader, List<Song> songs) {
+ if (songs.isEmpty()) { return; }
+ mSongs = songs;
+ notifyDataSetChanged();
+ }
+
+ @Override // LoaderCallbacks
+ public void onLoaderReset(Loader<List<Song>> loader) {
+ mSongs = Collections.emptyList();
+ notifyDataSetChanged();
+ mImageFetcher.flush();
+ }
+
+ private class Holder {
+ ImageView art;
+ TextView title;
+ TextView album;
+
+ Holder(View root) {
+ art = (ImageView)root.findViewById(R.id.album_art);
+ title = (TextView)root.findViewById(R.id.title);
+ album = (TextView)root.findViewById(R.id.album);
+ }
+ }
+}
\ No newline at end of file
import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
-import com.cyngn.eleven.ui.activities.ProfileActivity;
-
/**
* The {@link RecentlyListenedFragment} is used to display a a grid or list of
* recently listened to albums. In order to populate the this grid or list with
--- /dev/null
+package com.cyngn.eleven.ui.activities;
+
+import android.app.ActionBar;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.ImageView;
+import android.widget.ListView;
+
+import com.cyngn.eleven.Config;
+import com.cyngn.eleven.R;
+import com.cyngn.eleven.adapters.ArtistDetailAlbumAdapter;
+import com.cyngn.eleven.adapters.ArtistDetailSongAdapter;
+import com.cyngn.eleven.cache.ImageFetcher;
+
+import java.util.Locale;
+
+public class ArtistDetailActivity extends SlidingPanelActivity
+implements AbsListView.OnScrollListener {
+ private static final int ACTION_BAR_DEFAULT_OPACITY = 65;
+ private ImageView mHero;
+ private View mHeader;
+ private Drawable mActionBarBackground;
+
+ private ListView mSongs;
+ private ArtistDetailSongAdapter mSongAdapter;
+
+ private RecyclerView mAlbums;
+ private ArtistDetailAlbumAdapter mAlbumAdapter;
+
+ @Override
+ protected int getLayoutToInflate() { return R.layout.activity_artist_detail; }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle arguments = getIntent().getExtras();
+ String artistName = arguments.getString(Config.ARTIST_NAME);
+
+ setupActionBar(artistName);
+
+ ViewGroup root = (ViewGroup)findViewById(R.id.activity_base_content);
+ root.setPadding(0, 0, 0, 0); // clear default padding
+
+ setupSongList(root);
+ setupAlbumList();
+ setupHero(artistName);
+
+ LoaderManager lm = getSupportLoaderManager();
+ lm.initLoader(0, arguments, mAlbumAdapter);
+ lm.initLoader(1, arguments, mSongAdapter);
+ }
+
+ private void setupHero(String artistName) {
+ mHero = (ImageView)mHeader.findViewById(R.id.hero);
+ mHero.setContentDescription(artistName);
+ ImageFetcher.getInstance(this).loadArtistImage(artistName, mHero);
+ }
+
+ private void setupAlbumList() {
+ mAlbums = (RecyclerView) mHeader.findViewById(R.id.albums);
+ mAlbums.setHasFixedSize(true);
+ mAlbums.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
+ mAlbumAdapter = new ArtistDetailAlbumAdapter(this);
+ mAlbums.setAdapter(mAlbumAdapter);
+ }
+
+ private void setupSongList(ViewGroup root) {
+ mSongs = (ListView)root.findViewById(R.id.songs);
+ mHeader = (ViewGroup)LayoutInflater.from(this).
+ inflate(R.layout.artist_detail_header, mSongs, false);
+ mSongs.addHeaderView(mHeader);
+ mSongs.setOnScrollListener(this);
+ mSongAdapter = new ArtistDetailSongAdapter(this);
+ mSongs.setAdapter(mSongAdapter);
+ }
+
+ private void setupActionBar(String artistName) {
+ ActionBar actionBar = getActionBar();
+ actionBar.setTitle(artistName.toUpperCase(Locale.getDefault()));
+ actionBar.setIcon(R.drawable.ic_action_back);
+ actionBar.setHomeButtonEnabled(true);
+ // change action bar background to a drawable we can control
+ mActionBarBackground = new ColorDrawable(getResources().getColor(R.color.header_action_bar_color));
+ mActionBarBackground.setAlpha(ACTION_BAR_DEFAULT_OPACITY);
+ actionBar.setBackgroundDrawable(mActionBarBackground);
+ }
+
+ /** cause action bar icon tap to act like back -- boo-urns! */
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override // OnScrollListener
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ View child = view.getChildAt(0);
+ if(child == null) { return; }
+
+ float y = -child.getY();
+ if(y < 0) { return; }
+
+ float alpha = 255f;
+ // when the list view hits the top of the screen, y start counting up from 0
+ // again, so we check to see if enough items are on screen that the hero image
+ // is no longer visible, and assume opaque from there on out
+ if(visibleItemCount < 6) {
+ alpha = ACTION_BAR_DEFAULT_OPACITY +
+ ((255 - ACTION_BAR_DEFAULT_OPACITY) * y/(float)mHero.getHeight());
+ if(alpha > 255f) { alpha = 255f; }
+ }
+ mActionBarBackground.setAlpha((int)alpha);
+ }
+
+ @Override // OnScrollListener
+ public void onScrollStateChanged(AbsListView view, int scrollState) {}
+}
\ No newline at end of file
import com.cyngn.eleven.Config;
import com.cyngn.eleven.R;
+import com.cyngn.eleven.ui.activities.ArtistDetailActivity;
import com.cyngn.eleven.ui.activities.HomeActivity;
import com.cyngn.eleven.ui.activities.ProfileActivity;
import com.cyngn.eleven.ui.activities.SearchActivity;
* @param context The {@link Activity} to use.
* @param artistName The name of the artist
*/
- public static void openArtistProfile(final Activity context,
- final String artistName) {
-
+ public static void openArtistProfile(final Activity context, final String artistName) {
// Create a new bundle to transfer the artist info
final Bundle bundle = new Bundle();
bundle.putLong(Config.ID, MusicUtils.getIdForArtist(context, artistName));
bundle.putString(Config.ARTIST_NAME, artistName);
// Create the intent to launch the profile activity
- final Intent intent = new Intent(context, ProfileActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ final Intent intent = new Intent(context, ArtistDetailActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtras(bundle);
context.startActivity(intent);
}
import android.widget.ImageView;
import com.cyngn.eleven.R;
-import com.cyngn.eleven.ui.activities.ProfileActivity;
import com.cyngn.eleven.utils.ApolloUtils;
/**