summaryrefslogtreecommitdiffstats
path: root/src/org/lineageos/eleven/ui/fragments
diff options
context:
space:
mode:
authorMichael Bestas <mkbestas@lineageos.org>2018-01-22 21:02:42 +0200
committerMichael Bestas <mkbestas@lineageos.org>2018-01-23 19:35:52 +0200
commita907407e035b52e8dcdd4ac3ae48b533c7942d5a (patch)
treeef031b8478effd2fd828495832260359486950f4 /src/org/lineageos/eleven/ui/fragments
parentda200d369e4e43f2587273e9dd7af9c91048cf68 (diff)
downloadandroid_packages_apps_Eleven-a907407e035b52e8dcdd4ac3ae48b533c7942d5a.tar.gz
android_packages_apps_Eleven-a907407e035b52e8dcdd4ac3ae48b533c7942d5a.tar.bz2
android_packages_apps_Eleven-a907407e035b52e8dcdd4ac3ae48b533c7942d5a.zip
Eleven: rebrand step 1: update paths
Change-Id: Iab35e4024e20c48e7439e78d1c6efe0ef4f730ca
Diffstat (limited to 'src/org/lineageos/eleven/ui/fragments')
-rw-r--r--src/org/lineageos/eleven/ui/fragments/AlbumDetailFragment.java210
-rw-r--r--src/org/lineageos/eleven/ui/fragments/AlbumFragment.java323
-rw-r--r--src/org/lineageos/eleven/ui/fragments/ArtistDetailFragment.java252
-rw-r--r--src/org/lineageos/eleven/ui/fragments/ArtistFragment.java327
-rw-r--r--src/org/lineageos/eleven/ui/fragments/AudioPlayerFragment.java814
-rw-r--r--src/org/lineageos/eleven/ui/fragments/BaseFragment.java98
-rw-r--r--src/org/lineageos/eleven/ui/fragments/DetailFragment.java69
-rw-r--r--src/org/lineageos/eleven/ui/fragments/FadingBarFragment.java72
-rw-r--r--src/org/lineageos/eleven/ui/fragments/IChildFragment.java22
-rw-r--r--src/org/lineageos/eleven/ui/fragments/ISetupActionBar.java20
-rw-r--r--src/org/lineageos/eleven/ui/fragments/PlaylistDetailFragment.java420
-rw-r--r--src/org/lineageos/eleven/ui/fragments/PlaylistFragment.java231
-rw-r--r--src/org/lineageos/eleven/ui/fragments/QueueFragment.java478
-rw-r--r--src/org/lineageos/eleven/ui/fragments/RecentFragment.java136
-rw-r--r--src/org/lineageos/eleven/ui/fragments/SongFragment.java112
-rw-r--r--src/org/lineageos/eleven/ui/fragments/phone/MusicBrowserFragment.java43
-rw-r--r--src/org/lineageos/eleven/ui/fragments/phone/MusicBrowserPhoneFragment.java323
-rw-r--r--src/org/lineageos/eleven/ui/fragments/profile/BasicSongFragment.java324
-rw-r--r--src/org/lineageos/eleven/ui/fragments/profile/LastAddedFragment.java92
-rw-r--r--src/org/lineageos/eleven/ui/fragments/profile/SmartPlaylistFragment.java142
-rw-r--r--src/org/lineageos/eleven/ui/fragments/profile/TopTracksFragment.java118
21 files changed, 4626 insertions, 0 deletions
diff --git a/src/org/lineageos/eleven/ui/fragments/AlbumDetailFragment.java b/src/org/lineageos/eleven/ui/fragments/AlbumDetailFragment.java
new file mode 100644
index 0000000..5aeb70f
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/AlbumDetailFragment.java
@@ -0,0 +1,210 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.AlbumDetailSongAdapter;
+import com.cyanogenmod.eleven.adapters.DetailSongAdapter;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.cache.ImageFetcher;
+import com.cyanogenmod.eleven.model.Album;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.utils.AlbumPopupMenuHelper;
+import com.cyanogenmod.eleven.utils.GenreFetcher;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.utils.SongPopupMenuHelper;
+import com.cyanogenmod.eleven.widgets.IPopupMenuCallback;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+
+import java.util.List;
+
+public class AlbumDetailFragment extends DetailFragment implements IChildFragment {
+ private static final int LOADER_ID = 1;
+
+ private ListView mSongs;
+ private DetailSongAdapter mSongAdapter;
+ private TextView mAlbumDuration;
+ private TextView mGenre;
+ private ImageView mAlbumArt;
+ private PopupMenuHelper mSongMenuHelper;
+ private long mAlbumId;
+ private String mArtistName;
+ private String mAlbumName;
+ private LoadingEmptyContainer mLoadingEmptyContainer;
+
+ @Override
+ protected int getLayoutToInflate() {
+ return R.layout.activity_album_detail;
+ }
+
+ @Override
+ protected String getTitle() {
+ return getArguments().getString(Config.ARTIST_NAME);
+ }
+
+ @Override
+ protected void onViewCreated() {
+ super.onViewCreated();
+
+ Bundle arguments = getArguments();
+ String artistName = arguments.getString(Config.ARTIST_NAME);
+
+ setupPopupMenuHelper();
+ setupHeader(artistName, arguments);
+ setupSongList();
+
+ LoaderManager lm = getLoaderManager();
+ lm.initLoader(LOADER_ID, arguments, mSongAdapter);
+ }
+
+ @Override // DetailFragment
+ protected PopupMenuHelper createActionMenuHelper() {
+ return new AlbumPopupMenuHelper(getActivity(), getChildFragmentManager()) {
+ public Album getAlbum(int position) {
+ return new Album(mAlbumId, mAlbumName, mArtistName, -1, null);
+ }
+ };
+ }
+
+ @Override // DetailFragment
+ protected int getShuffleTitleId() { return R.string.menu_shuffle_album; }
+
+ @Override // DetailFragment
+ protected void playShuffled() {
+ MusicUtils.playAlbum(getActivity(), mAlbumId, -1, true);
+ }
+
+ private void setupHeader(String artist, Bundle arguments) {
+ mAlbumId = arguments.getLong(Config.ID);
+ mArtistName = artist;
+ mAlbumName = arguments.getString(Config.NAME);
+ String year = arguments.getString(Config.ALBUM_YEAR);
+ int songCount = arguments.getInt(Config.SONG_COUNT);
+
+ mAlbumArt = (ImageView)mRootView.findViewById(R.id.album_art);
+ mAlbumArt.setContentDescription(mAlbumName);
+ ImageFetcher.getInstance(getActivity()).loadAlbumImage(artist, mAlbumName, mAlbumId, mAlbumArt);
+
+ TextView title = (TextView)mRootView.findViewById(R.id.title);
+ title.setText(mAlbumName);
+
+ setupCountAndYear(mRootView, year, songCount);
+
+ // will be updated once we have song data
+ mAlbumDuration = (TextView)mRootView.findViewById(R.id.duration);
+ mGenre = (TextView)mRootView.findViewById(R.id.genre);
+ }
+
+ private void setupCountAndYear(View root, String year, int songCount) {
+ TextView songCountAndYear = (TextView)root.findViewById(R.id.song_count_and_year);
+ if(songCount > 0) {
+ String countText = getResources().
+ getQuantityString(R.plurals.Nsongs, songCount, songCount);
+ if(year == null) {
+ songCountAndYear.setText(countText);
+ } else {
+ songCountAndYear.setText(getString(R.string.combine_two_strings, countText, year));
+ }
+ } else if(year != null) {
+ songCountAndYear.setText(year);
+ }
+ }
+
+ private void setupPopupMenuHelper() {
+ mSongMenuHelper = new SongPopupMenuHelper(getActivity(), getChildFragmentManager()) {
+ @Override
+ public Song getSong(int position) {
+ return mSongAdapter.getItem(position);
+ }
+
+ @Override
+ protected long getSourceId() {
+ return mAlbumId;
+ }
+
+ @Override
+ protected Config.IdType getSourceType() {
+ return Config.IdType.Album;
+ }
+ };
+ }
+
+ private void setupSongList() {
+ mSongs = (ListView)mRootView.findViewById(R.id.songs);
+ mSongAdapter = new AlbumDetailSongAdapter(getActivity(), this) {
+ @Override
+ protected void onLoading() {
+ mLoadingEmptyContainer.showLoading();
+ }
+
+ @Override
+ protected void onNoResults() {
+ getContainingActivity().postRemoveFragment(AlbumDetailFragment.this);
+ }
+ };
+ mSongAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mSongMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ mSongs.setAdapter(mSongAdapter);
+ mSongs.setOnItemClickListener(mSongAdapter);
+ mLoadingEmptyContainer =
+ (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
+ mSongs.setEmptyView(mLoadingEmptyContainer);
+ }
+
+ /** called back by song loader */
+ public void update(List<Song> songs) {
+ /** compute total run time for album */
+ int duration = 0;
+ for(Song s : songs) { duration += s.mDuration; }
+ mAlbumDuration.setText(MusicUtils.makeLongTimeString(getActivity(), duration));
+
+ /** use the first song on the album to get a genre */
+ if(!songs.isEmpty()) {
+ GenreFetcher.fetch(getActivity(), (int) songs.get(0).mSongId, mGenre);
+ }
+ }
+
+ @Override
+ public void restartLoader() {
+ getLoaderManager().restartLoader(LOADER_ID, getArguments(), mSongAdapter);
+ ImageFetcher.getInstance(getActivity()).loadAlbumImage(mArtistName, mAlbumName, mAlbumId,
+ mAlbumArt);
+ }
+
+ @Override
+ public void onMetaChanged() {
+ super.onMetaChanged();
+
+ mSongAdapter.setCurrentlyPlayingTrack(MusicUtils.getCurrentTrack());
+ }
+
+ @Override
+ public PagerAdapter.MusicFragments getMusicFragmentParent() {
+ return PagerAdapter.MusicFragments.ALBUM;
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/AlbumFragment.java b/src/org/lineageos/eleven/ui/fragments/AlbumFragment.java
new file mode 100644
index 0000000..f645ddd
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/AlbumFragment.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+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.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.GridView;
+
+import com.cyanogenmod.eleven.MusicStateListener;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.AlbumAdapter;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.loaders.AlbumLoader;
+import com.cyanogenmod.eleven.model.Album;
+import com.cyanogenmod.eleven.recycler.RecycleHolder;
+import com.cyanogenmod.eleven.sectionadapter.SectionCreator;
+import com.cyanogenmod.eleven.sectionadapter.SectionListContainer;
+import com.cyanogenmod.eleven.ui.activities.BaseActivity;
+import com.cyanogenmod.eleven.ui.fragments.phone.MusicBrowserFragment;
+import com.cyanogenmod.eleven.utils.AlbumPopupMenuHelper;
+import com.cyanogenmod.eleven.utils.ApolloUtils;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.NavUtils;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.widgets.IPopupMenuCallback;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+
+/**
+ * This class is used to display all of the albums on a user's device.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class AlbumFragment extends MusicBrowserFragment implements
+ LoaderCallbacks<SectionListContainer<Album>>, OnScrollListener,
+ OnItemClickListener, MusicStateListener {
+
+ /**
+ * Grid view column count. ONE - list, TWO - normal grid, FOUR - landscape
+ */
+ private static final int TWO = 2, FOUR = 4;
+
+ /**
+ * Fragment UI
+ */
+ private ViewGroup mRootView;
+
+ /**
+ * The adapter for the grid
+ */
+ private AlbumAdapter mAdapter;
+
+ /**
+ * The grid view
+ */
+ private GridView mGridView;
+
+ /**
+ * Pop up menu helper
+ */
+ private PopupMenuHelper mPopupMenuHelper;
+
+ /**
+ * This holds the loading progress bar as well as the no results message
+ */
+ private LoadingEmptyContainer mLoadingEmptyContainer;
+
+ @Override
+ public int getLoaderId() {
+ return PagerAdapter.MusicFragments.ALBUM.ordinal();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mPopupMenuHelper = new AlbumPopupMenuHelper(getActivity(), getFragmentManager()) {
+ public Album getAlbum(int position) {
+ return mAdapter.getItem(position);
+ }
+ };
+
+ int layout = R.layout.grid_items_normal;
+
+ mAdapter = new AlbumAdapter(getActivity(), layout);
+ mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mPopupMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ mRootView = (ViewGroup)inflater.inflate(R.layout.grid_base, null);
+ initGridView();
+
+ // Register the music status listener
+ ((BaseActivity)getActivity()).setMusicStateListenerListener(this);
+
+ return mRootView;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Enable the options menu
+ setHasOptionsMenu(true);
+ // Start the loader
+ initLoader(null, this);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ ((BaseActivity)getActivity()).removeMusicStateListenerListener(this);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPause() {
+ super.onPause();
+ mAdapter.flush();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onScrollStateChanged(final AbsListView view, final int scrollState) {
+ // Pause disk cache access to ensure smoother scrolling
+ if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
+ mAdapter.setPauseDiskCache(true);
+ } else {
+ mAdapter.setPauseDiskCache(false);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onItemClick(final AdapterView<?> parent, final View view, final int position,
+ final long id) {
+ Album album = mAdapter.getItem(position);
+ NavUtils.openAlbumProfile(getActivity(), album.mAlbumName, album.mArtistName, album.mAlbumId);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<SectionListContainer<Album>> onCreateLoader(final int id, final Bundle args) {
+ mLoadingEmptyContainer.showLoading();
+ // if we ever decide to add section headers for grid items, we can pass a compartor
+ // instead of null
+ return new SectionCreator<Album>(getActivity(), new AlbumLoader(getActivity()), null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<SectionListContainer<Album>> loader,
+ final SectionListContainer<Album> data) {
+ if (data.mListResults.isEmpty()) {
+ mAdapter.unload();
+ mLoadingEmptyContainer.showNoResults();
+ return;
+ }
+
+ mAdapter.setData(data.mListResults);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<SectionListContainer<Album>> loader) {
+ // Clear the data in the adapter
+ mAdapter.unload();
+ }
+
+ /**
+ * Scrolls the list to the currently playing album when the user touches the
+ * header in the {@link TitlePageIndicator}.
+ */
+ public void scrollToCurrentAlbum() {
+ final int currentAlbumPosition = getItemPositionByAlbum();
+
+ if (currentAlbumPosition != 0) {
+ mGridView.setSelection(currentAlbumPosition);
+ }
+ }
+
+ /**
+ * @return The position of an item in the list or grid based on the id of
+ * the currently playing album.
+ */
+ private int getItemPositionByAlbum() {
+ final long albumId = MusicUtils.getCurrentAlbumId();
+ if (mAdapter == null) {
+ return 0;
+ }
+
+ int position = mAdapter.getItemPosition(albumId);
+
+ // if for some reason we don't find the item, just jump to the top
+ if (position < 0) {
+ return 0;
+ }
+
+ return position;
+ }
+
+ /**
+ * Restarts the loader.
+ */
+ public void refresh() {
+ // Wait a moment for the preference to change.
+ SystemClock.sleep(10);
+ restartLoader();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onScroll(final AbsListView view, final int firstVisibleItem,
+ final int visibleItemCount, final int totalItemCount) {
+ // Nothing to do
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void restartLoader() {
+ // Update the list when the user deletes any items
+ restartLoader(null, this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onMetaChanged() {
+ // Nothing to do
+ }
+
+ @Override
+ public void onPlaylistChanged() {
+ // Nothing to do
+ }
+
+ /**
+ * Sets up various helpers for both the list and grid
+ *
+ * @param list The list or grid
+ */
+ private void initAbsListView(final AbsListView list) {
+ // Release any references to the recycled Views
+ list.setRecyclerListener(new RecycleHolder());
+ // Show the albums and songs from the selected artist
+ list.setOnItemClickListener(this);
+ // To help make scrolling smooth
+ list.setOnScrollListener(this);
+ }
+
+ /**
+ * Sets up the grid view
+ */
+ private void initGridView() {
+ int columns = ApolloUtils.isLandscape(getActivity()) ? FOUR : TWO;
+ mAdapter.setNumColumns(columns);
+ // Initialize the grid
+ mGridView = (GridView)mRootView.findViewById(R.id.grid_base);
+ // Set the data behind the grid
+ mGridView.setAdapter(mAdapter);
+ // Set up the helpers
+ initAbsListView(mGridView);
+ mGridView.setNumColumns(columns);
+
+ // Show progress bar
+ mLoadingEmptyContainer = (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
+ mGridView.setEmptyView(mLoadingEmptyContainer);
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/ArtistDetailFragment.java b/src/org/lineageos/eleven/ui/fragments/ArtistDetailFragment.java
new file mode 100644
index 0000000..21bd556
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/ArtistDetailFragment.java
@@ -0,0 +1,252 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.os.Build;
+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.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.ListView;
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.ArtistDetailAlbumAdapter;
+import com.cyanogenmod.eleven.adapters.ArtistDetailSongAdapter;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.cache.ImageFetcher;
+import com.cyanogenmod.eleven.menu.FragmentMenuItems;
+import com.cyanogenmod.eleven.model.Album;
+import com.cyanogenmod.eleven.model.Artist;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.utils.AlbumPopupMenuHelper;
+import com.cyanogenmod.eleven.utils.ArtistPopupMenuHelper;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.utils.SongPopupMenuHelper;
+import com.cyanogenmod.eleven.widgets.IPopupMenuCallback;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+
+import java.util.TreeSet;
+
+public class ArtistDetailFragment extends FadingBarFragment implements IChildFragment {
+ private final int ALBUM_LOADER_ID = 0;
+ private final int SONG_LOADER_ID = 1;
+
+ private long mArtistId;
+ private String mArtistName;
+
+ private ImageView mHero;
+ private View mHeader;
+
+ private ListView mSongs;
+ private ArtistDetailSongAdapter mSongAdapter;
+
+ private RecyclerView mAlbums;
+ private ArtistDetailAlbumAdapter mAlbumAdapter;
+
+ private PopupMenuHelper mSongPopupMenuHelper;
+ private PopupMenuHelper mAlbumPopupMenuHelper;
+
+ private LoadingEmptyContainer mLoadingEmptyContainer;
+
+ @Override
+ protected int getLayoutToInflate() { return R.layout.activity_artist_detail; }
+
+ @Override
+ protected String getTitle() {
+ return getArguments().getString(Config.ARTIST_NAME);
+ }
+
+ protected long getArtistId() {
+ return getArguments().getLong(Config.ID);
+ }
+
+ @Override
+ protected void onViewCreated() {
+ super.onViewCreated();
+
+ getContainingActivity().setFragmentPadding(false);
+
+ Bundle arguments = getArguments();
+ mArtistName = arguments.getString(Config.ARTIST_NAME);
+ mArtistId = arguments.getLong(Config.ID);
+
+ setupPopupMenuHelpers();
+ setupSongList();
+ setupAlbumList();
+ setupHero(mArtistName);
+
+ LoaderManager lm = getLoaderManager();
+ lm.initLoader(ALBUM_LOADER_ID, arguments, mAlbumAdapter);
+ lm.initLoader(SONG_LOADER_ID, arguments, mSongAdapter);
+ }
+
+ @Override // DetailFragment
+ protected PopupMenuHelper createActionMenuHelper() {
+ return new ArtistPopupMenuHelper(getActivity(), getChildFragmentManager()) {
+ public Artist getArtist(int position) {
+ return new Artist(mArtistId, mArtistName, 0, 0);
+ }
+ };
+ }
+
+ @Override // DetailFragment
+ protected int getShuffleTitleId() { return R.string.menu_shuffle_artist; }
+
+ @Override // DetailFragment
+ protected void playShuffled() {
+ MusicUtils.playArtist(getActivity(), mArtistId, -1, true);
+ }
+
+ private void setupHero(String artistName) {
+ mHero = (ImageView)mHeader.findViewById(R.id.hero);
+ mHero.setContentDescription(artistName);
+ // initiate loading the artist image
+ // since the artist image needs to be scaled to the image view bounds, we need to wait till the first layout
+ // traversal to be able to get the image view dimensions in the helper method that scales the image
+ mHero.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ mHero.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ } else {
+ mHero.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ }
+ ImageFetcher.getInstance(getActivity()).loadArtistImage(mArtistName, mHero, true);
+ }
+ });
+ }
+
+ private void setupAlbumList() {
+ mAlbums = (RecyclerView) mHeader.findViewById(R.id.albums);
+ mAlbums.setHasFixedSize(true);
+ mAlbums.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false));
+ mAlbumAdapter = new ArtistDetailAlbumAdapter(getActivity());
+ mAlbumAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mAlbumPopupMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ mAlbums.setAdapter(mAlbumAdapter);
+ }
+
+ private void setupSongList() {
+ mSongs = (ListView)mRootView.findViewById(R.id.songs);
+ mHeader = LayoutInflater.from(getActivity()).
+ inflate(R.layout.artist_detail_header, mSongs, false);
+ mSongs.addHeaderView(mHeader);
+ mSongs.setOnScrollListener(this);
+ mSongAdapter = new ArtistDetailSongAdapter(getActivity()) {
+ @Override
+ protected void onLoading() {
+ mLoadingEmptyContainer.showLoading();
+ }
+
+ @Override
+ protected void onNoResults() {
+ // no results - because the user deleted the last item - pop our fragment
+ // from the stack
+ getContainingActivity().postRemoveFragment(ArtistDetailFragment.this);
+ }
+ };
+ mSongAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mSongPopupMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ mSongs.setAdapter(mSongAdapter);
+ mSongs.setOnItemClickListener(mSongAdapter);
+ mLoadingEmptyContainer =
+ (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
+ mSongs.setEmptyView(mLoadingEmptyContainer);
+ }
+
+ private void setupPopupMenuHelpers() {
+ mSongPopupMenuHelper = new SongPopupMenuHelper(getActivity(), getChildFragmentManager()) {
+ @Override
+ public Song getSong(int position) {
+ return mSongAdapter.getItem(position);
+ }
+
+ @Override
+ protected long getSourceId() {
+ return getArtistId();
+ }
+
+ @Override
+ protected Config.IdType getSourceType() {
+ return Config.IdType.Artist;
+ }
+
+ @Override
+ protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
+ super.updateMenuIds(type, set);
+
+ // since we are already on the artist page, this item doesn't make sense
+ set.remove(FragmentMenuItems.MORE_BY_ARTIST);
+ }
+ };
+
+ mAlbumPopupMenuHelper = new AlbumPopupMenuHelper(getActivity(), getChildFragmentManager()) {
+ @Override
+ public Album getAlbum(int position) {
+ return mAlbumAdapter.getItem(position);
+ }
+
+ @Override
+ protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
+ super.updateMenuIds(type, set);
+
+ // since we are already on the artist page, this item doesn't make sense
+ set.remove(FragmentMenuItems.MORE_BY_ARTIST);
+ }
+ };
+ }
+
+ // TODO: change this class to use the same header strategy as PlaylistDetail
+ protected int getHeaderHeight() { return mHero.getHeight(); }
+
+ protected void setHeaderPosition(float y) { }
+
+ @Override
+ public void restartLoader() {
+ Bundle arguments = getArguments();
+ LoaderManager lm = getLoaderManager();
+ lm.restartLoader(ALBUM_LOADER_ID, arguments, mAlbumAdapter);
+ lm.restartLoader(SONG_LOADER_ID, arguments, mSongAdapter);
+
+ ImageFetcher.getInstance(getActivity()).loadArtistImage(mArtistName, mHero, true);
+ }
+
+ @Override
+ public void onMetaChanged() {
+ super.onMetaChanged();
+
+ mSongAdapter.setCurrentlyPlayingTrack(MusicUtils.getCurrentTrack());
+ }
+
+ @Override
+ public PagerAdapter.MusicFragments getMusicFragmentParent() {
+ return PagerAdapter.MusicFragments.ARTIST;
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/ArtistFragment.java b/src/org/lineageos/eleven/ui/fragments/ArtistFragment.java
new file mode 100644
index 0000000..100c55a
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/ArtistFragment.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.v4.app.Fragment;
+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.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import com.cyanogenmod.eleven.MusicStateListener;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.ArtistAdapter;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.loaders.ArtistLoader;
+import com.cyanogenmod.eleven.model.Artist;
+import com.cyanogenmod.eleven.recycler.RecycleHolder;
+import com.cyanogenmod.eleven.sectionadapter.SectionAdapter;
+import com.cyanogenmod.eleven.sectionadapter.SectionCreator;
+import com.cyanogenmod.eleven.sectionadapter.SectionListContainer;
+import com.cyanogenmod.eleven.ui.activities.BaseActivity;
+import com.cyanogenmod.eleven.ui.fragments.phone.MusicBrowserFragment;
+import com.cyanogenmod.eleven.utils.ArtistPopupMenuHelper;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.NavUtils;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.utils.SectionCreatorUtils;
+import com.cyanogenmod.eleven.utils.SectionCreatorUtils.IItemCompare;
+import com.cyanogenmod.eleven.widgets.IPopupMenuCallback;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+
+/**
+ * This class is used to display all of the artists on a user's device.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class ArtistFragment extends MusicBrowserFragment implements
+ LoaderCallbacks<SectionListContainer<Artist>>,
+ OnScrollListener, OnItemClickListener, MusicStateListener {
+
+ /**
+ * Fragment UI
+ */
+ private ViewGroup mRootView;
+
+ /**
+ * The adapter for the grid
+ */
+ private SectionAdapter<Artist, ArtistAdapter> mAdapter;
+
+ /**
+ * The list view
+ */
+ private ListView mListView;
+
+ /**
+ * Pop up menu helper
+ */
+ private PopupMenuHelper mPopupMenuHelper;
+
+ /**
+ * Loading container and no results container
+ */
+ private LoadingEmptyContainer mLoadingEmptyContainer;
+
+ /**
+ * Empty constructor as per the {@link Fragment} documentation
+ */
+ public ArtistFragment() {
+ }
+
+ @Override
+ public int getLoaderId() {
+ return PagerAdapter.MusicFragments.ARTIST.ordinal();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mPopupMenuHelper = new ArtistPopupMenuHelper(getActivity(), getFragmentManager()) {
+ @Override
+ public Artist getArtist(int position) {
+ return mAdapter.getTItem(position);
+ }
+ };
+
+ // Create the adapter
+ final int layout = R.layout.list_item_normal;
+ ArtistAdapter adapter = new ArtistAdapter(getActivity(), layout);
+ mAdapter = new SectionAdapter<Artist, ArtistAdapter>(getActivity(), adapter);
+ mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mPopupMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ // The View for the fragment's UI
+ mRootView = (ViewGroup)inflater.inflate(R.layout.list_base, null);
+ initListView();
+
+ // Register the music status listener
+ ((BaseActivity)getActivity()).setMusicStateListenerListener(this);
+
+ return mRootView;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ ((BaseActivity)getActivity()).removeMusicStateListenerListener(this);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Enable the options menu
+ setHasOptionsMenu(true);
+ // Start the loader
+ initLoader(null, this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPause() {
+ super.onPause();
+ mAdapter.flush();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onScrollStateChanged(final AbsListView view, final int scrollState) {
+ // Pause disk cache access to ensure smoother scrolling
+ if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
+ mAdapter.getUnderlyingAdapter().setPauseDiskCache(true);
+ } else {
+ mAdapter.getUnderlyingAdapter().setPauseDiskCache(false);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onItemClick(final AdapterView<?> parent, final View view, final int position,
+ final long id) {
+ Artist artist = mAdapter.getTItem(position);
+ NavUtils.openArtistProfile(getActivity(), artist.mArtistName);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<SectionListContainer<Artist>> onCreateLoader(final int id, final Bundle args) {
+ mLoadingEmptyContainer.showLoading();
+ final Context context = getActivity();
+ IItemCompare<Artist> comparator = SectionCreatorUtils.createArtistComparison(context);
+ return new SectionCreator<Artist>(getActivity(), new ArtistLoader(context), comparator);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<SectionListContainer<Artist>> loader,
+ final SectionListContainer<Artist> data) {
+ if (data.mListResults.isEmpty()) {
+ mAdapter.unload();
+ mLoadingEmptyContainer.showNoResults();
+ return;
+ }
+
+ mAdapter.setData(data);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<SectionListContainer<Artist>> loader) {
+ // Clear the data in the adapter
+ mAdapter.unload();
+ }
+
+ /**
+ * Scrolls the list to the currently playing artist when the user touches
+ * the header in the {@link TitlePageIndicator}.
+ */
+ public void scrollToCurrentArtist() {
+ final int currentArtistPosition = getItemPositionByArtist();
+
+ if (currentArtistPosition != 0) {
+ mListView.setSelection(currentArtistPosition);
+ }
+ }
+
+ /**
+ * @return The position of an item in the list or grid based on the name of
+ * the currently playing artist.
+ */
+ private int getItemPositionByArtist() {
+ final long artistId = MusicUtils.getCurrentArtistId();
+ if (mAdapter == null) {
+ return 0;
+ }
+
+ int position = mAdapter.getItemPosition(artistId);
+
+ // if for some reason we don't find the item, just jump to the top
+ if (position < 0) {
+ return 0;
+ }
+
+ return position;
+ }
+
+ /**
+ * Restarts the loader.
+ */
+ public void refresh() {
+ // Wait a moment for the preference to change.
+ SystemClock.sleep(10);
+ restartLoader();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onScroll(final AbsListView view, final int firstVisibleItem,
+ final int visibleItemCount, final int totalItemCount) {
+ // Nothing to do
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void restartLoader() {
+ // Update the list when the user deletes any items
+ restartLoader(null, this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onMetaChanged() {
+ // Nothing to do
+ }
+
+ @Override
+ public void onPlaylistChanged() {
+ // Nothing to do
+ }
+
+ /**
+ * Sets up various helpers for both the list and grid
+ *
+ * @param list The list or grid
+ */
+ private void initAbsListView(final AbsListView list) {
+ // Release any references to the recycled Views
+ list.setRecyclerListener(new RecycleHolder());
+ // Show the albums and songs from the selected artist
+ list.setOnItemClickListener(this);
+ // To help make scrolling smooth
+ list.setOnScrollListener(this);
+ }
+
+ /**
+ * Sets up the list view
+ */
+ private void initListView() {
+ // Initialize the grid
+ mListView = (ListView)mRootView.findViewById(R.id.list_base);
+ // Set the data behind the list
+ mListView.setAdapter(mAdapter);
+ // set the loading and empty view container
+ mLoadingEmptyContainer = (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
+ mListView.setEmptyView(mLoadingEmptyContainer);
+ // Set up the helpers
+ initAbsListView(mListView);
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/AudioPlayerFragment.java b/src/org/lineageos/eleven/ui/fragments/AudioPlayerFragment.java
new file mode 100644
index 0000000..58cecd0
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/AudioPlayerFragment.java
@@ -0,0 +1,814 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.graphics.Outline;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.ViewPager;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.widget.ImageView;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+
+import com.cyanogenmod.eleven.MusicPlaybackService;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.AlbumArtPagerAdapter;
+import com.cyanogenmod.eleven.cache.ImageFetcher;
+import com.cyanogenmod.eleven.loaders.NowPlayingCursor;
+import com.cyanogenmod.eleven.loaders.QueueLoader;
+import com.cyanogenmod.eleven.menu.CreateNewPlaylist;
+import com.cyanogenmod.eleven.menu.DeleteDialog;
+import com.cyanogenmod.eleven.menu.FragmentMenuItems;
+import com.cyanogenmod.eleven.ui.activities.HomeActivity;
+import com.cyanogenmod.eleven.utils.ApolloUtils;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.NavUtils;
+import com.cyanogenmod.eleven.utils.PreferenceUtils;
+import com.cyanogenmod.eleven.widgets.BrowseButton;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+import com.cyanogenmod.eleven.widgets.NoResultsContainer;
+import com.cyanogenmod.eleven.widgets.PlayPauseProgressButton;
+import com.cyanogenmod.eleven.widgets.QueueButton;
+import com.cyanogenmod.eleven.widgets.RepeatButton;
+import com.cyanogenmod.eleven.widgets.RepeatingImageButton;
+import com.cyanogenmod.eleven.widgets.ShuffleButton;
+import com.cyanogenmod.eleven.widgets.VisualizerView;
+
+import java.lang.ref.WeakReference;
+
+import static com.cyanogenmod.eleven.utils.MusicUtils.mService;
+
+public class AudioPlayerFragment extends Fragment implements ServiceConnection {
+ private static final String TAG = AudioPlayerFragment.class.getSimpleName();
+
+ /**
+ * Used to keep context menu items from bleeding into other fragments
+ */
+ private static final int GROUP_ID = 15;
+
+ // fragment view
+ private ViewGroup mRootView;
+
+ // Header views
+ private TextView mSongTitle;
+ private TextView mArtistName;
+
+ // Playlist Button
+ private ImageView mAddToPlaylistButton;
+
+ // Menu Button
+ private ImageView mMenuButton;
+
+ // Message to refresh the time
+ private static final int REFRESH_TIME = 1;
+
+ // The service token
+ private MusicUtils.ServiceToken mToken;
+
+ // Play pause and progress button
+ private PlayPauseProgressButton mPlayPauseProgressButton;
+
+ // Repeat button
+ private RepeatButton mRepeatButton;
+
+ // Shuffle button
+ private ShuffleButton mShuffleButton;
+
+ // Previous button
+ private RepeatingImageButton mPreviousButton;
+
+ // Next button
+ private RepeatingImageButton mNextButton;
+
+ // Album art ListView
+ private ViewPager mAlbumArtViewPager;
+ private LoadingEmptyContainer mQueueEmpty;
+
+ private AlbumArtPagerAdapter mAlbumArtPagerAdapter;
+
+ // Current time
+ private TextView mCurrentTime;
+
+ // Total time
+ private TextView mTotalTime;
+
+ // Visualizer View
+ private VisualizerView mVisualizerView;
+
+ // Broadcast receiver
+ private PlaybackStatus mPlaybackStatus;
+
+ // Handler used to update the current time
+ private TimeHandler mTimeHandler;
+
+ // Image cache
+ private ImageFetcher mImageFetcher;
+
+ // popup menu for pressing the menu icon
+ private PopupMenu mPopupMenu;
+
+ // Lyrics text view
+ private TextView mLyricsText;
+
+ private long mSelectedId = -1;
+
+ private boolean mIsPaused = false;
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // Control the media volume
+ getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ // Bind Apollo's service
+ mToken = MusicUtils.bindToService(getActivity(), this);
+
+ // Initialize the image fetcher/cache
+ mImageFetcher = ApolloUtils.getImageFetcher(getActivity());
+
+ // Initialize the handler used to update the current time
+ mTimeHandler = new TimeHandler(this);
+
+ // Initialize the broadcast receiver
+ mPlaybackStatus = new PlaybackStatus(this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ // The View for the fragment's UI
+ mRootView = (ViewGroup) inflater.inflate(R.layout.activity_player_fragment, null);
+
+ // Header title values
+ initHeaderBar();
+
+ initPlaybackControls();
+
+ mVisualizerView = (VisualizerView) mRootView.findViewById(R.id.visualizerView);
+ mVisualizerView.initialize(getActivity());
+ updateVisualizerPowerSaveMode();
+
+ mLyricsText = (TextView) mRootView.findViewById(R.id.audio_player_lyrics);
+
+ return mRootView;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onServiceConnected(final ComponentName name, final IBinder service) {
+ // Set the playback drawables
+ updatePlaybackControls();
+ // Setup the adapter
+ createAndSetAdapter();
+ // Current info
+ updateNowPlayingInfo();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ final IntentFilter filter = new IntentFilter();
+ // Play and pause changes
+ filter.addAction(MusicPlaybackService.PLAYSTATE_CHANGED);
+ // Shuffle and repeat changes
+ filter.addAction(MusicPlaybackService.SHUFFLEMODE_CHANGED);
+ filter.addAction(MusicPlaybackService.REPEATMODE_CHANGED);
+ // Track changes
+ filter.addAction(MusicPlaybackService.META_CHANGED);
+ // Update a list, probably the playlist fragment's
+ filter.addAction(MusicPlaybackService.REFRESH);
+ // Listen to changes to the entire queue
+ filter.addAction(MusicPlaybackService.QUEUE_CHANGED);
+ // Listen for lyrics text for the audio track
+ filter.addAction(MusicPlaybackService.NEW_LYRICS);
+ // Listen for power save mode changed
+ filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ // Register the intent filters
+ getActivity().registerReceiver(mPlaybackStatus, filter);
+ // Refresh the current time
+ final long next = refreshCurrentTime();
+ queueNextRefresh(next);
+
+ // resumes the update callback for the play pause progress button
+ mPlayPauseProgressButton.resume();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ // pause the update callback for the play pause progress button
+ mPlayPauseProgressButton.pause();
+
+ mImageFetcher.flush();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ mIsPaused = false;
+ mTimeHandler.removeMessages(REFRESH_TIME);
+ // Unbind from the service
+ if (mService != null) {
+ MusicUtils.unbindFromService(mToken);
+ mToken = null;
+ }
+
+ // Unregister the receiver
+ try {
+ getActivity().unregisterReceiver(mPlaybackStatus);
+ } catch (final Throwable e) {
+ //$FALL-THROUGH$
+ }
+ }
+
+ /**
+ * Initializes the header bar
+ */
+ private void initHeaderBar() {
+ View headerBar = mRootView.findViewById(R.id.audio_player_header);
+ final int bottomActionBarHeight =
+ getResources().getDimensionPixelSize(R.dimen.bottom_action_bar_height);
+
+ headerBar.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ // since we only want the top and bottom shadows, pad the horizontal width
+ // to hide the shadows. Can't seem to find a better way to do this
+ int padWidth = (int)(0.2f * view.getWidth());
+ outline.setRect(-padWidth, -bottomActionBarHeight, view.getWidth() + padWidth,
+ view.getHeight());
+ }
+ });
+
+ // Title text
+ mSongTitle = (TextView) mRootView.findViewById(R.id.header_bar_song_title);
+ mArtistName = (TextView) mRootView.findViewById(R.id.header_bar_artist_title);
+
+ // Buttons
+ // Search Button
+ View v = mRootView.findViewById(R.id.header_bar_search_button);
+ v.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ NavUtils.openSearch(getActivity(), "");
+ }
+ });
+
+ // Add to Playlist Button
+ // Setup the playlist button - add a click listener to show the context
+ mAddToPlaylistButton = (ImageView) mRootView.findViewById(R.id.header_bar_add_button);
+
+ // Create the context menu when requested
+ mAddToPlaylistButton.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ MusicUtils.makePlaylistMenu(getActivity(), GROUP_ID, menu);
+ menu.setHeaderTitle(R.string.add_to_playlist);
+ }
+ });
+
+ // add a click listener to show the context
+ mAddToPlaylistButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // save the current track id
+ mSelectedId = MusicUtils.getCurrentAudioId();
+ mAddToPlaylistButton.showContextMenu();
+ }
+ });
+
+ // Add the menu button
+ // menu button
+ mMenuButton = (ImageView) mRootView.findViewById(R.id.header_bar_menu_button);
+ mMenuButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showPopupMenu();
+ }
+ });
+ }
+
+ /**
+ * Initializes the items in the now playing screen
+ */
+ private void initPlaybackControls() {
+ mPlayPauseProgressButton = (PlayPauseProgressButton)mRootView.findViewById(R.id.playPauseProgressButton);
+ mPlayPauseProgressButton.setDragEnabled(true);
+ mShuffleButton = (ShuffleButton)mRootView.findViewById(R.id.action_button_shuffle);
+ mRepeatButton = (RepeatButton)mRootView.findViewById(R.id.action_button_repeat);
+ mPreviousButton = (RepeatingImageButton)mRootView.findViewById(R.id.action_button_previous);
+ mNextButton = (RepeatingImageButton)mRootView.findViewById(R.id.action_button_next);
+
+ // Album art view pager
+ mAlbumArtViewPager = (ViewPager)mRootView.findViewById(R.id.audio_player_album_art_viewpager);
+ mAlbumArtViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ super.onPageSelected(position);
+
+ int currentPosition = 0;
+ if (MusicUtils.getShuffleMode() == MusicPlaybackService.SHUFFLE_NONE) {
+ // if we aren't shuffling, base the position on the queue position
+ currentPosition = MusicUtils.getQueuePosition();
+ } else {
+ // if we are shuffling, use the history size as the position
+ currentPosition = MusicUtils.getQueueHistorySize();
+ }
+
+ // check if we are going to next or previous track
+ if (position - currentPosition == 1) {
+ MusicUtils.asyncNext(getActivity());
+ } else if (position - currentPosition == -1) {
+ MusicUtils.previous(getActivity(), true);
+ } else if (currentPosition != position) {
+ Log.w(TAG, "Unexpected page position of " + position
+ + " when current is: " + currentPosition);
+ }
+ }
+ });
+ // view to show in place of album art if queue is empty
+ mQueueEmpty = (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
+ setupNoResultsContainer(mQueueEmpty.getNoResultsContainer());
+
+ // Current time
+ mCurrentTime = (TextView)mRootView.findViewById(R.id.audio_player_current_time);
+ // Total time
+ mTotalTime = (TextView)mRootView.findViewById(R.id.audio_player_total_time);
+
+ // Set the repeat listener for the previous button
+ mPreviousButton.setRepeatListener(mRewindListener);
+ // Set the repeat listener for the next button
+ mNextButton.setRepeatListener(mFastForwardListener);
+
+ mPlayPauseProgressButton.enableAndShow();
+ }
+
+ private void setupNoResultsContainer(NoResultsContainer empty) {
+ int color = getResources().getColor(R.color.no_results_light);
+ empty.setTextColor(color);
+ empty.setMainText(R.string.empty_queue_main);
+ empty.setSecondaryText(R.string.empty_queue_secondary);
+ }
+
+ /**
+ * Sets the track name, album name, and album art.
+ */
+ private void updateNowPlayingInfo() {
+ // Set the track name
+ mSongTitle.setText(MusicUtils.getTrackName());
+ mArtistName.setText(MusicUtils.getArtistName());
+
+ // Set the total time
+ String totalTime = MusicUtils.makeShortTimeString(getActivity(), MusicUtils.duration() / 1000);
+ if (!mTotalTime.getText().equals(totalTime)) {
+ mTotalTime.setText(totalTime);
+ }
+
+ if (MusicUtils.getRepeatMode() == MusicPlaybackService.REPEAT_CURRENT) {
+ // we are repeating 1 so just jump to the 1st and only item
+ mAlbumArtViewPager.setCurrentItem(0, false);
+ } else if (MusicUtils.getShuffleMode() == MusicPlaybackService.SHUFFLE_NONE) {
+ // we are playing in-order, base the position on the queue position
+ mAlbumArtViewPager.setCurrentItem(MusicUtils.getQueuePosition(), true);
+ } else {
+ // if we are shuffling, just based our index based on the history
+ mAlbumArtViewPager.setCurrentItem(MusicUtils.getQueueHistorySize(), true);
+ }
+
+ // Update the current time
+ queueNextRefresh(1);
+ }
+
+ /**
+ * This creates the adapter based on the repeat and shuffle configuration and sets it into the
+ * page adapter
+ */
+ private void createAndSetAdapter() {
+ mAlbumArtPagerAdapter = new AlbumArtPagerAdapter(getChildFragmentManager());
+
+ int repeatMode = MusicUtils.getRepeatMode();
+ int targetSize = 0;
+ int targetIndex = 0;
+ int queueSize = MusicUtils.getQueueSize();
+
+ if (repeatMode == MusicPlaybackService.REPEAT_CURRENT) {
+ targetSize = 1;
+ targetIndex = 0;
+ } else if (MusicUtils.getShuffleMode() == MusicPlaybackService.SHUFFLE_NONE) {
+ // if we aren't shuffling, use the queue to determine where we are
+ targetSize = queueSize;
+ targetIndex = MusicUtils.getQueuePosition();
+ } else {
+ // otherwise, set it to the max history size
+ targetSize = MusicPlaybackService.MAX_HISTORY_SIZE;
+ targetIndex = MusicUtils.getQueueHistorySize();
+ }
+
+ mAlbumArtPagerAdapter.setPlaylistLength(targetSize);
+ mAlbumArtViewPager.setAdapter(mAlbumArtPagerAdapter);
+ mAlbumArtViewPager.setCurrentItem(targetIndex);
+
+ if(queueSize == 0) {
+ mAlbumArtViewPager.setVisibility(View.GONE);
+ mQueueEmpty.showNoResults();
+ mAddToPlaylistButton.setVisibility(View.GONE);
+ } else {
+ mAlbumArtViewPager.setVisibility(View.VISIBLE);
+ mQueueEmpty.hideAll();
+ mAddToPlaylistButton.setVisibility(View.VISIBLE);
+ }
+ }
+
+ /**
+ * Sets the correct drawable states for the playback controls.
+ */
+ private void updatePlaybackControls() {
+ // Set the play and pause image
+ mPlayPauseProgressButton.getPlayPauseButton().updateState();
+ // Set the shuffle image
+ mShuffleButton.updateShuffleState();
+ // Set the repeat image
+ mRepeatButton.updateRepeatState();
+ }
+
+ /**
+ * @param delay When to update
+ */
+ private void queueNextRefresh(final long delay) {
+ if (!mIsPaused) {
+ final Message message = mTimeHandler.obtainMessage(REFRESH_TIME);
+ mTimeHandler.removeMessages(REFRESH_TIME);
+ mTimeHandler.sendMessageDelayed(message, delay);
+ }
+ }
+
+ /**
+ * Used to seek forwards or backwards in time through the current track
+ *
+ * @param repcnt The repeat count
+ * @param delta The long press duration
+ * @param forwards Whether it was seeking forwards or backwards
+ */
+ private void seekRelative(final int repcnt, long delta, boolean forwards) {
+ if (mService == null) {
+ return;
+ }
+ if (repcnt > 0) {
+ final long EXTRA_FAST_CUTOFF = 10000;
+ if (delta < EXTRA_FAST_CUTOFF) {
+ // seek at 10x speed for the first 5 seconds
+ delta = delta * 10;
+ } else {
+ // seek at 40x after that
+ delta = EXTRA_FAST_CUTOFF * 10 + (delta - EXTRA_FAST_CUTOFF) * 40;
+ }
+
+ MusicUtils.seekRelative(forwards ? delta : -delta);
+
+ refreshCurrentTime();
+ }
+ }
+
+ private void refreshCurrentTimeText(final long pos) {
+ if (mPlayPauseProgressButton.isDragging()) {
+ mCurrentTime.setText(MusicUtils.makeShortTimeString(getActivity(),
+ mPlayPauseProgressButton.getDragProgressInMs() / 1000));
+ } else {
+ mCurrentTime.setText(MusicUtils.makeShortTimeString(getActivity(), pos / 1000));
+ }
+ }
+
+ /* Used to update the current time string */
+ private long refreshCurrentTime() {
+ if (mService == null) {
+ return MusicUtils.UPDATE_FREQUENCY_MS;
+ }
+ try {
+ final long pos = MusicUtils.position();
+ if (pos >= 0 && MusicUtils.duration() > 0) {
+ refreshCurrentTimeText(pos);
+
+ if (mPlayPauseProgressButton.isDragging()) {
+ mCurrentTime.setVisibility(View.VISIBLE);
+ return MusicUtils.UPDATE_FREQUENCY_FAST_MS;
+ } else if (MusicUtils.isPlaying()) {
+ mCurrentTime.setVisibility(View.VISIBLE);
+
+ // calculate the number of milliseconds until the next full second,
+ // so the counter can be updated at just the right time
+ return Math.max(20, 1000 - pos % 1000);
+ } else {
+ // blink the counter
+ final int vis = mCurrentTime.getVisibility();
+ mCurrentTime.setVisibility(vis == View.INVISIBLE ? View.VISIBLE
+ : View.INVISIBLE);
+ }
+ } else {
+ mCurrentTime.setText("--:--");
+ }
+ } catch (final Exception ignored) {
+ if (ignored.getMessage() != null) {
+ Log.e(TAG, ignored.getMessage());
+ }
+ }
+ return MusicUtils.UPDATE_FREQUENCY_MS;
+ }
+
+ /**
+ * Used to scan backwards through the track
+ */
+ private final RepeatingImageButton.RepeatListener mRewindListener = new RepeatingImageButton.RepeatListener() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onRepeat(final View v, final long howlong, final int repcnt) {
+ seekRelative(repcnt, howlong, false);
+ }
+ };
+
+ /**
+ * Used to scan ahead through the track
+ */
+ private final RepeatingImageButton.RepeatListener mFastForwardListener = new RepeatingImageButton.RepeatListener() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onRepeat(final View v, final long howlong, final int repcnt) {
+ seekRelative(repcnt, howlong, true);
+ }
+ };
+
+ public void showPopupMenu() {
+ // create the popup menu
+ if (mPopupMenu == null) {
+ mPopupMenu = new PopupMenu(getActivity(), mMenuButton);
+ mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ return onPopupMenuItemClick(item);
+ }
+ });
+ }
+
+ final Menu menu = mPopupMenu.getMenu();
+ final MenuInflater inflater = mPopupMenu.getMenuInflater();
+ menu.clear();
+
+ // Shuffle all
+ inflater.inflate(R.menu.shuffle_all, menu);
+ if (MusicUtils.getQueueSize() > 0) {
+ // ringtone, and equalizer
+ inflater.inflate(R.menu.audio_player, menu);
+
+ if (!NavUtils.hasEffectsPanel(getActivity())) {
+ menu.removeItem(R.id.menu_audio_player_equalizer);
+ }
+
+ // save queue/clear queue
+ inflater.inflate(R.menu.queue, menu);
+ }
+ // Settings
+ inflater.inflate(R.menu.activity_base, menu);
+
+ // show the popup
+ mPopupMenu.show();
+ }
+
+ public boolean onPopupMenuItemClick(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_shuffle_all:
+ // Shuffle all the songs
+ MusicUtils.shuffleAll(getActivity());
+ return true;
+ case R.id.menu_audio_player_ringtone:
+ // Set the current track as a ringtone
+ MusicUtils.setRingtone(getActivity(), MusicUtils.getCurrentAudioId());
+ return true;
+ case R.id.menu_audio_player_equalizer:
+ // Sound effects
+ NavUtils.openEffectsPanel(getActivity(), HomeActivity.EQUALIZER);
+ return true;
+ case R.id.menu_settings:
+ // Settings
+ NavUtils.openSettings(getActivity());
+ return true;
+ case R.id.menu_audio_player_more_by_artist:
+ NavUtils.openArtistProfile(getActivity(), MusicUtils.getArtistName());
+ return true;
+ case R.id.menu_audio_player_delete:
+ // Delete current song
+ DeleteDialog.newInstance(MusicUtils.getTrackName(), new long[]{
+ MusicUtils.getCurrentAudioId()
+ }, null).show(getActivity().getSupportFragmentManager(), "DeleteDialog");
+ return true;
+ case R.id.menu_save_queue:
+ NowPlayingCursor queue = (NowPlayingCursor) QueueLoader
+ .makeQueueCursor(getActivity());
+ CreateNewPlaylist.getInstance(MusicUtils.getSongListForCursor(queue)).show(
+ getFragmentManager(), "CreatePlaylist");
+ queue.close();
+ return true;
+ case R.id.menu_clear_queue:
+ MusicUtils.clearQueue();
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+ }
+
+ public void dismissPopupMenu() {
+ if (mPopupMenu != null) {
+ mPopupMenu.dismiss();
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ if (item.getGroupId() == GROUP_ID) {
+ switch (item.getItemId()) {
+ case FragmentMenuItems.NEW_PLAYLIST:
+ CreateNewPlaylist.getInstance(new long[]{
+ mSelectedId
+ }).show(getFragmentManager(), "CreatePlaylist");
+ return true;
+ case FragmentMenuItems.PLAYLIST_SELECTED:
+ final long mPlaylistId = item.getIntent().getLongExtra("playlist", 0);
+ MusicUtils.addToPlaylist(getActivity(), new long[]{
+ mSelectedId
+ }, mPlaylistId);
+ return true;
+ default:
+ break;
+ }
+ }
+
+ return super.onContextItemSelected(item);
+ }
+
+ public void onLyrics(String lyrics) {
+ if (TextUtils.isEmpty(lyrics)
+ || !PreferenceUtils.getInstance(getActivity()).getShowLyrics()) {
+ mLyricsText.animate().alpha(0).setDuration(200);
+ } else {
+ lyrics = lyrics.replace("\n", "<br/>");
+ Spanned span = Html.fromHtml(lyrics);
+ mLyricsText.setText(span);
+
+ mLyricsText.animate().alpha(1).setDuration(200);
+ }
+ }
+
+ public void setVisualizerVisible(boolean visible) {
+ if (visible && PreferenceUtils.getInstance(getActivity()).getShowVisualizer()) {
+ if (PreferenceUtils.canRecordAudio(getActivity())) {
+ mVisualizerView.setVisible(true);
+ } else {
+ PreferenceUtils.requestRecordAudio(getActivity());
+ }
+ } else {
+ mVisualizerView.setVisible(false);
+ }
+ }
+
+ public void updateVisualizerPowerSaveMode() {
+ PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
+ mVisualizerView.setPowerSaveMode(pm.isPowerSaveMode());
+ }
+
+ public void setVisualizerColor(int color) {
+ mVisualizerView.setColor(color);
+ }
+
+ /**
+ * Used to update the current time string
+ */
+ private static final class TimeHandler extends Handler {
+
+ private final WeakReference<AudioPlayerFragment> mAudioPlayer;
+
+ /**
+ * Constructor of <code>TimeHandler</code>
+ */
+ public TimeHandler(final AudioPlayerFragment player) {
+ mAudioPlayer = new WeakReference<AudioPlayerFragment>(player);
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ switch (msg.what) {
+ case REFRESH_TIME:
+ final long next = mAudioPlayer.get().refreshCurrentTime();
+ mAudioPlayer.get().queueNextRefresh(next);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ /**
+ * Used to monitor the state of playback
+ */
+ private static final class PlaybackStatus extends BroadcastReceiver {
+
+ private final WeakReference<AudioPlayerFragment> mReference;
+
+ /**
+ * Constructor of <code>PlaybackStatus</code>
+ */
+ public PlaybackStatus(final AudioPlayerFragment fragment) {
+ mReference = new WeakReference<AudioPlayerFragment>(fragment);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ final AudioPlayerFragment audioPlayerFragment = mReference.get();
+ final String action = intent.getAction();
+ if (action.equals(MusicPlaybackService.META_CHANGED)) {
+ // if we are repeating current and the track has changed, re-create the adapter
+ if (MusicUtils.getRepeatMode() == MusicPlaybackService.REPEAT_CURRENT) {
+ mReference.get().createAndSetAdapter();
+ }
+
+ // Current info
+ audioPlayerFragment.updateNowPlayingInfo();
+ audioPlayerFragment.dismissPopupMenu();
+ } else if (action.equals(MusicPlaybackService.PLAYSTATE_CHANGED)) {
+ // Set the play and pause image
+ audioPlayerFragment.mPlayPauseProgressButton.getPlayPauseButton().updateState();
+ audioPlayerFragment.mVisualizerView.setPlaying(MusicUtils.isPlaying());
+ } else if (action.equals(MusicPlaybackService.REPEATMODE_CHANGED)
+ || action.equals(MusicPlaybackService.SHUFFLEMODE_CHANGED)) {
+ // Set the repeat image
+ audioPlayerFragment.mRepeatButton.updateRepeatState();
+ // Set the shuffle image
+ audioPlayerFragment.mShuffleButton.updateShuffleState();
+
+ // Update the queue
+ audioPlayerFragment.createAndSetAdapter();
+ } else if (action.equals(MusicPlaybackService.QUEUE_CHANGED)) {
+ audioPlayerFragment.createAndSetAdapter();
+ } else if (action.equals(MusicPlaybackService.NEW_LYRICS)) {
+ audioPlayerFragment.onLyrics(intent.getStringExtra("lyrics"));
+ } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
+ audioPlayerFragment.updateVisualizerPowerSaveMode();
+ }
+ }
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/BaseFragment.java b/src/org/lineageos/eleven/ui/fragments/BaseFragment.java
new file mode 100644
index 0000000..5a5885a
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/BaseFragment.java
@@ -0,0 +1,98 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.cyanogenmod.eleven.MusicStateListener;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.ui.activities.HomeActivity;
+
+public abstract class BaseFragment extends Fragment implements MusicStateListener,
+ ISetupActionBar {
+
+ protected ViewGroup mRootView;
+
+ protected abstract String getTitle();
+ protected abstract int getLayoutToInflate();
+
+ protected boolean needsElevatedActionBar() {
+ return true;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setRetainInstance(true);
+ }
+
+ @Override
+ public void setupActionBar() {
+ getContainingActivity().setupActionBar(getTitle());
+ getContainingActivity().setActionBarAlpha(255);
+ getContainingActivity().setFragmentPadding(true);
+ getContainingActivity().setActionBarElevation(needsElevatedActionBar());
+ }
+
+ protected HomeActivity getContainingActivity() {
+ return (HomeActivity) getActivity();
+ }
+
+ @Override
+ public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ // The View for the fragment's UI
+ mRootView = (ViewGroup) inflater.inflate(getLayoutToInflate(), null);
+ // set the background color
+ mRootView.setBackgroundColor(getResources().getColor(R.color.background_color));
+ // eat any touches that fall through to the root so they aren't
+ // passed on to fragments "behind" the current one.
+ mRootView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent me) { return true; }
+ });
+
+ setupActionBar();
+ onViewCreated();
+
+ return mRootView;
+ }
+
+ protected void onViewCreated() {
+ getContainingActivity().setMusicStateListenerListener(this);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ getContainingActivity().removeMusicStateListenerListener(this);
+ }
+
+ @Override
+ public void onMetaChanged() {
+
+ }
+
+ @Override
+ public void onPlaylistChanged() {
+
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/DetailFragment.java b/src/org/lineageos/eleven/ui/fragments/DetailFragment.java
new file mode 100644
index 0000000..43e5274
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/DetailFragment.java
@@ -0,0 +1,69 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+
+public abstract class DetailFragment extends BaseFragment {
+ protected PopupMenuHelper mActionMenuHelper;
+
+ /** create the popup menu helper used by the type of item
+ * for which this is a detail screen */
+ protected abstract PopupMenuHelper createActionMenuHelper();
+ /** menu title for the shuffle option for this screen */
+ protected abstract int getShuffleTitleId();
+ /** action to take if the shuffle menu is selected */
+ protected abstract void playShuffled();
+
+ @Override
+ protected void onViewCreated() {
+ super.onViewCreated();
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.shuffle_item, menu);
+ menu.findItem(R.id.menu_shuffle_item).setTitle(getShuffleTitleId());
+
+ // use the same popup menu to provide actions for the item
+ // represented by this detail screen as would be used elsewhere
+ mActionMenuHelper = createActionMenuHelper();
+ mActionMenuHelper.onPreparePopupMenu(0);
+ mActionMenuHelper.createPopupMenu(menu);
+
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if(item.getItemId() == R.id.menu_shuffle_item) {
+ playShuffled();
+ return true;
+ }
+
+ // delegate to the popup menu that represents the item
+ // for which this is a detail screen
+ if(mActionMenuHelper.onMenuItemClick(item)) { return true; }
+
+ return super.onOptionsItemSelected(item);
+ }
+} \ No newline at end of file
diff --git a/src/org/lineageos/eleven/ui/fragments/FadingBarFragment.java b/src/org/lineageos/eleven/ui/fragments/FadingBarFragment.java
new file mode 100644
index 0000000..81c4009
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/FadingBarFragment.java
@@ -0,0 +1,72 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+
+import com.cyanogenmod.eleven.ui.activities.HomeActivity;
+
+public abstract class FadingBarFragment extends DetailFragment implements OnScrollListener {
+ protected static final int ACTION_BAR_DEFAULT_OPACITY = 100;
+
+ @Override
+ public void setupActionBar() {
+ super.setupActionBar();
+
+ getContainingActivity().setActionBarAlpha(ACTION_BAR_DEFAULT_OPACITY);
+ getContainingActivity().setFragmentPadding(false);
+ }
+
+ protected abstract int getHeaderHeight();
+
+ protected abstract void setHeaderPosition(float y);
+
+ @Override // OnScrollListener
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+ View firstChild = view.getChildAt(0);
+ if (firstChild == null) {
+ return;
+ }
+
+ float firstChildY = firstChild.getY();
+
+ int alpha = 255;
+ if (firstVisibleItem == 0) {
+ // move header to current top of list
+ setHeaderPosition(firstChildY);
+ // calculate alpha for the action bar
+ alpha = ACTION_BAR_DEFAULT_OPACITY +
+ (int)((255 - ACTION_BAR_DEFAULT_OPACITY) * -firstChildY /
+ (float)(firstChild.getHeight()));
+ if(alpha > 255) { alpha = 255; }
+ } else {
+ // header off screen
+ setHeaderPosition(-getHeaderHeight());
+ }
+
+ HomeActivity home = getContainingActivity();
+ if (home != null && home.getTopFragment() == this) {
+ home.setActionBarAlpha(alpha);
+ }
+ }
+
+ @Override // OnScrollListener
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/IChildFragment.java b/src/org/lineageos/eleven/ui/fragments/IChildFragment.java
new file mode 100644
index 0000000..f1b5fb7
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/IChildFragment.java
@@ -0,0 +1,22 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+
+public interface IChildFragment {
+ PagerAdapter.MusicFragments getMusicFragmentParent();
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/ISetupActionBar.java b/src/org/lineageos/eleven/ui/fragments/ISetupActionBar.java
new file mode 100644
index 0000000..6ce191b
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/ISetupActionBar.java
@@ -0,0 +1,20 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+public interface ISetupActionBar {
+ public void setupActionBar();
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/PlaylistDetailFragment.java b/src/org/lineageos/eleven/ui/fragments/PlaylistDetailFragment.java
new file mode 100644
index 0000000..4461b27
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/PlaylistDetailFragment.java
@@ -0,0 +1,420 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.adapters.ProfileSongAdapter;
+import com.cyanogenmod.eleven.cache.ImageFetcher;
+import com.cyanogenmod.eleven.dragdrop.DragSortListView;
+import com.cyanogenmod.eleven.dragdrop.DragSortListView.DragScrollProfile;
+import com.cyanogenmod.eleven.dragdrop.DragSortListView.DropListener;
+import com.cyanogenmod.eleven.dragdrop.DragSortListView.RemoveListener;
+import com.cyanogenmod.eleven.loaders.PlaylistSongLoader;
+import com.cyanogenmod.eleven.menu.FragmentMenuItems;
+import com.cyanogenmod.eleven.model.Playlist;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.recycler.RecycleHolder;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.PlaylistPopupMenuHelper;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper.PopupMenuType;
+import com.cyanogenmod.eleven.utils.SongPopupMenuHelper;
+import com.cyanogenmod.eleven.widgets.IPopupMenuCallback;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+import com.cyanogenmod.eleven.widgets.NoResultsContainer;
+
+import java.util.List;
+import java.util.TreeSet;
+
+public class PlaylistDetailFragment extends FadingBarFragment implements
+ LoaderCallbacks<List<Song>>, OnItemClickListener, DropListener,
+ RemoveListener, DragScrollProfile, IChildFragment {
+
+ /**
+ * LoaderCallbacks identifier
+ */
+ private static final int LOADER = 0;
+
+ private DragSortListView mListView;
+ private ProfileSongAdapter mAdapter;
+
+ private View mHeaderContainer;
+ private ImageView mPlaylistImageView;
+
+ private LoadingEmptyContainer mLoadingEmptyContainer;
+
+ private TextView mNumberOfSongs;
+ private TextView mDurationOfPlaylist;
+
+ /**
+ * The Id of the playlist the songs belong to
+ */
+ private long mPlaylistId;
+ private String mPlaylistName;
+
+ /**
+ * Pop up menu helper
+ */
+ private PopupMenuHelper mPopupMenuHelper;
+
+ @Override
+ protected String getTitle() { return mPlaylistName; }
+
+ @Override
+ protected int getLayoutToInflate() {
+ return R.layout.playlist_detail;
+ }
+
+ @Override
+ protected void onViewCreated() {
+ super.onViewCreated();
+ setupHero();
+ setupSongList();
+ }
+
+ private void lookupName() {
+ mPlaylistName = MusicUtils.getNameForPlaylist(getActivity(), mPlaylistId);
+ }
+
+ @Override // DetailFragment
+ protected PopupMenuHelper createActionMenuHelper() {
+ return new PlaylistPopupMenuHelper(
+ getActivity(), getChildFragmentManager(), PopupMenuType.Playlist) {
+ public Playlist getPlaylist(int position) {
+ return new Playlist(mPlaylistId, getTitle(), 0);
+ }
+ };
+ }
+
+ @Override // DetailFragment
+ protected int getShuffleTitleId() { return R.string.menu_shuffle_playlist; }
+
+ @Override // DetailFragment
+ protected void playShuffled() {
+ MusicUtils.playPlaylist(getActivity(), mPlaylistId, true);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ LoaderManager lm = getLoaderManager();
+ lm.initLoader(0, getArguments(), this);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mPopupMenuHelper = new SongPopupMenuHelper(getActivity(), getFragmentManager()) {
+ @Override
+ public Song getSong(int position) {
+ if (position == 0) {
+ return null;
+ }
+
+ return mAdapter.getItem(position);
+ }
+
+ @Override
+ protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
+ super.updateMenuIds(type, set);
+
+ set.add(FragmentMenuItems.REMOVE_FROM_PLAYLIST);
+ set.remove(FragmentMenuItems.DELETE);
+ }
+
+ @Override
+ protected long getSourceId() {
+ return mPlaylistId;
+ }
+
+ @Override
+ protected Config.IdType getSourceType() {
+ return Config.IdType.Playlist;
+ }
+
+ @Override
+ protected void removeFromPlaylist() {
+ mAdapter.remove(mSong);
+ mAdapter.buildCache();
+ mAdapter.notifyDataSetChanged();
+ MusicUtils.removeFromPlaylist(getActivity(), mSong.mSongId, mPlaylistId);
+ getLoaderManager().restartLoader(LOADER, null, PlaylistDetailFragment.this);
+ }
+ };
+
+ mPlaylistId = getArguments().getLong(Config.ID);
+ lookupName();
+ }
+
+ private void setupHero() {
+ mPlaylistImageView = (ImageView)mRootView.findViewById(R.id.image);
+ mHeaderContainer = mRootView.findViewById(R.id.playlist_header);
+ mNumberOfSongs = (TextView)mRootView.findViewById(R.id.number_of_songs_text);
+ mDurationOfPlaylist = (TextView)mRootView.findViewById(R.id.duration_text);
+
+ final ImageFetcher imageFetcher = ImageFetcher.getInstance(getActivity());
+ imageFetcher.loadPlaylistArtistImage(mPlaylistId, mPlaylistImageView);
+ }
+
+ private void setupSongList() {
+ mListView = (DragSortListView) mRootView.findViewById(R.id.list_base);
+ mListView.setOnScrollListener(PlaylistDetailFragment.this);
+
+ mAdapter = new ProfileSongAdapter(
+ mPlaylistId,
+ getActivity(),
+ R.layout.edit_track_list_item,
+ R.layout.faux_playlist_header
+ );
+ mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mPopupMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ mListView.setAdapter(mAdapter);
+ // Release any references to the recycled Views
+ mListView.setRecyclerListener(new RecycleHolder());
+ // Play the selected song
+ mListView.setOnItemClickListener(this);
+ // Set the drop listener
+ mListView.setDropListener(this);
+ // Set the swipe to remove listener
+ mListView.setRemoveListener(this);
+ // Quick scroll while dragging
+ mListView.setDragScrollProfile(this);
+
+ // Adjust the progress bar padding to account for the header
+ int padTop = getResources().getDimensionPixelSize(R.dimen.playlist_detail_header_height);
+ mRootView.findViewById(R.id.progressbar).setPadding(0, padTop, 0, 0);
+
+ // set the loading and empty view container
+ mLoadingEmptyContainer =
+ (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
+ setupNoResultsContainer(mLoadingEmptyContainer.getNoResultsContainer());
+ mListView.setEmptyView(mLoadingEmptyContainer);
+ }
+
+ private void setupNoResultsContainer(final NoResultsContainer container) {
+ container.setMainText(R.string.empty_playlist_main);
+ container.setSecondaryText(R.string.empty_playlist_secondary);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public float getSpeed(final float w, final long t) {
+ if (w > 0.8f) {
+ return mAdapter.getCount() / 0.001f;
+ } else {
+ return 10.0f * w;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void remove(final int which) {
+ if (which == 0) {
+ return;
+ }
+
+ Song song = mAdapter.getItem(which);
+ mAdapter.remove(song);
+ mAdapter.buildCache();
+ mAdapter.notifyDataSetChanged();
+ final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", mPlaylistId);
+ getActivity().getContentResolver().delete(uri,
+ MediaStore.Audio.Playlists.Members.AUDIO_ID + "=" + song.mSongId,
+ null);
+
+ MusicUtils.refresh();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void drop(int from, int to) {
+ from = Math.max(ProfileSongAdapter.NUM_HEADERS, from);
+ to = Math.max(ProfileSongAdapter.NUM_HEADERS, to);
+
+ Song song = mAdapter.getItem(from);
+ mAdapter.remove(song);
+ mAdapter.insert(song, to);
+ mAdapter.buildCache();
+ mAdapter.notifyDataSetChanged();
+
+ final int realFrom = from - ProfileSongAdapter.NUM_HEADERS;
+ final int realTo = to - ProfileSongAdapter.NUM_HEADERS;
+ MediaStore.Audio.Playlists.Members.moveItem(getActivity().getContentResolver(),
+ mPlaylistId, realFrom, realTo);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onItemClick(final AdapterView<?> parent, final View view, final int position,
+ final long id) {
+ if (position == 0) {
+ return;
+ }
+ Cursor cursor = PlaylistSongLoader.makePlaylistSongCursor(getActivity(),
+ mPlaylistId);
+ final long[] list = MusicUtils.getSongListForCursor(cursor);
+ MusicUtils.playAll(getActivity(), list, position - ProfileSongAdapter.NUM_HEADERS,
+ mPlaylistId, Config.IdType.Playlist, false);
+ cursor.close();
+ cursor = null;
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ super.onScrollStateChanged(view, scrollState);
+
+ if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
+ mAdapter.setPauseDiskCache(true);
+ } else {
+ mAdapter.setPauseDiskCache(false);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ protected int getHeaderHeight() { return mHeaderContainer.getHeight(); }
+
+ protected void setHeaderPosition(float y) {
+ // Offset the header height to account for the faux header
+ y = y - getResources().getDimension(R.dimen.header_bar_height);
+ mHeaderContainer.setY(y);
+ }
+
+ @Override
+ public Loader<List<Song>> onCreateLoader(int i, Bundle bundle) {
+ mLoadingEmptyContainer.showLoading();
+
+ return new PlaylistSongLoader(getActivity(), mPlaylistId);
+ }
+
+ @Override
+ public void onLoadFinished(final Loader<List<Song>> loader, final List<Song> data) {
+ if (data.isEmpty()) {
+ mLoadingEmptyContainer.showNoResults();
+
+ // hide the header container
+ mHeaderContainer.setVisibility(View.INVISIBLE);
+
+ // Start fresh
+ mAdapter.unload();
+ } else {
+ // show the header container
+ mHeaderContainer.setVisibility(View.VISIBLE);
+
+ // pause notifying the adapter and make changes before re-enabling it so that the list
+ // view doesn't reset to the top of the list
+ mAdapter.setNotifyOnChange(false);
+ // Start fresh
+ mAdapter.unload();
+ // Return the correct count
+ mAdapter.addAll(data);
+ // build the cache
+ mAdapter.buildCache();
+ // re-enable the notify by calling notify dataset changes
+ mAdapter.notifyDataSetChanged();
+ // set the number of songs
+ String numberOfSongs = MusicUtils.makeLabel(getActivity(), R.plurals.Nsongs,
+ data.size());
+ mNumberOfSongs.setText(numberOfSongs);
+
+ long duration = 0;
+
+ // Add the data to the adapter
+ for (final Song song : data) {
+ duration += song.mDuration;
+ }
+
+ // set the duration
+ String durationString = MusicUtils.makeLongTimeString(getActivity(), duration);
+ mDurationOfPlaylist.setText(durationString);
+ }
+ }
+
+ @Override
+ public void onLoaderReset(final Loader<List<Song>> loader) {
+ // Clear the data in the adapter
+ mAdapter.unload();
+ }
+
+ @Override
+ public void restartLoader() {
+ lookupName(); // playlist name may have changed
+ if(mPlaylistName == null) {
+ // if name is null, we've been deleted, so close the this fragment
+ getContainingActivity().postRemoveFragment(this);
+ return;
+ }
+
+ // since onCreateOptionsMenu can be called after onCreate it is possible for
+ // mActionMenuHelper to be null. In this case, don't bother updating the Name since when
+ // it does create it, it will use the updated name anyways
+ if (mActionMenuHelper != null) {
+ // update action bar title and popup menu handler
+ ((PlaylistPopupMenuHelper) mActionMenuHelper).updateName(mPlaylistName);
+ }
+
+ getContainingActivity().setActionBarTitle(mPlaylistName);
+ // and reload the song list
+ getLoaderManager().restartLoader(0, getArguments(), this);
+ }
+
+ @Override
+ public void onMetaChanged() {
+ super.onMetaChanged();
+
+ mAdapter.setCurrentlyPlayingTrack(MusicUtils.getCurrentTrack());
+ }
+
+ @Override
+ public void onPlaylistChanged() {
+ super.onPlaylistChanged();
+
+ restartLoader();
+ }
+
+ @Override
+ public PagerAdapter.MusicFragments getMusicFragmentParent() {
+ return PagerAdapter.MusicFragments.PLAYLIST;
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/PlaylistFragment.java b/src/org/lineageos/eleven/ui/fragments/PlaylistFragment.java
new file mode 100644
index 0000000..aba8dbd
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/PlaylistFragment.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+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.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import com.cyanogenmod.eleven.Config.SmartPlaylistType;
+import com.cyanogenmod.eleven.MusicStateListener;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.adapters.PlaylistAdapter;
+import com.cyanogenmod.eleven.loaders.PlaylistLoader;
+import com.cyanogenmod.eleven.model.Playlist;
+import com.cyanogenmod.eleven.recycler.RecycleHolder;
+import com.cyanogenmod.eleven.ui.activities.BaseActivity;
+import com.cyanogenmod.eleven.ui.fragments.phone.MusicBrowserFragment;
+import com.cyanogenmod.eleven.utils.NavUtils;
+import com.cyanogenmod.eleven.utils.PlaylistPopupMenuHelper;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.widgets.IPopupMenuCallback;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+
+import java.util.List;
+
+/**
+ * This class is used to display all of the playlists on a user's device.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class PlaylistFragment extends MusicBrowserFragment implements
+ LoaderCallbacks<List<Playlist>>,
+ OnItemClickListener, MusicStateListener {
+
+ /**
+ * The adapter for the list
+ */
+ private PlaylistAdapter mAdapter;
+
+ /**
+ * The list view
+ */
+ private ListView mListView;
+
+ /**
+ * Pop up menu helper
+ */
+ private PopupMenuHelper mPopupMenuHelper;
+
+ /**
+ * This holds the loading progress bar as well as the no results message
+ */
+ private LoadingEmptyContainer mLoadingEmptyContainer;
+
+ /**
+ * Empty constructor as per the {@link Fragment} documentation
+ */
+ public PlaylistFragment() {
+ }
+
+ @Override
+ public int getLoaderId() {
+ return PagerAdapter.MusicFragments.PLAYLIST.ordinal();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mPopupMenuHelper = new PlaylistPopupMenuHelper(getActivity(), getFragmentManager(), null) {
+ @Override
+ public Playlist getPlaylist(int position) {
+ return mAdapter.getItem(position);
+ }
+ };
+
+ // Create the adapter
+ mAdapter = new PlaylistAdapter(getActivity());
+ mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mPopupMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ // The View for the fragment's UI
+ final ViewGroup rootView = (ViewGroup)inflater.inflate(R.layout.list_base, null);
+ // Initialize the list
+ mListView = (ListView)rootView.findViewById(R.id.list_base);
+ // Set the data behind the grid
+ mListView.setAdapter(mAdapter);
+ // Release any references to the recycled Views
+ mListView.setRecyclerListener(new RecycleHolder());
+ // Play the selected song
+ mListView.setOnItemClickListener(this);
+ // Setup the loading and empty state
+ mLoadingEmptyContainer =
+ (LoadingEmptyContainer)rootView.findViewById(R.id.loading_empty_container);
+ mListView.setEmptyView(mLoadingEmptyContainer);
+
+ // Register the music status listener
+ ((BaseActivity)getActivity()).setMusicStateListenerListener(this);
+
+ return rootView;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ ((BaseActivity)getActivity()).removeMusicStateListenerListener(this);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Enable the options menu
+ setHasOptionsMenu(true);
+ // Start the loader
+ initLoader(null, this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onItemClick(final AdapterView<?> parent, final View view, final int position,
+ final long id) {
+ Playlist playlist = mAdapter.getItem(position);
+
+ SmartPlaylistType playlistType = SmartPlaylistType.getTypeById(playlist.mPlaylistId);
+ if (playlistType != null) {
+ NavUtils.openSmartPlaylist(getActivity(), playlistType);
+ } else {
+ NavUtils.openPlaylist(getActivity(), playlist.mPlaylistId, playlist.mPlaylistName);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<List<Playlist>> onCreateLoader(final int id, final Bundle args) {
+ // show the loading progress bar
+ mLoadingEmptyContainer.showLoading();
+ return new PlaylistLoader(getActivity());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<List<Playlist>> loader, final List<Playlist> data) {
+ // Check for any errors
+ if (data.isEmpty()) {
+ mLoadingEmptyContainer.showNoResults();
+ return;
+ }
+
+ // Start fresh
+ mAdapter.unload();
+ // Add the data to the adpater
+ for (final Playlist playlist : data) {
+ mAdapter.add(playlist);
+ }
+ // Build the cache
+ mAdapter.buildCache();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<List<Playlist>> loader) {
+ // Clear the data in the adapter
+ mAdapter.unload();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void restartLoader() {
+ restartLoader(null, this);
+ }
+
+ @Override
+ public void onPlaylistChanged() {
+ restartLoader();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onMetaChanged() {
+ // Nothing to do
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/QueueFragment.java b/src/org/lineageos/eleven/ui/fragments/QueueFragment.java
new file mode 100644
index 0000000..35229bc
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/QueueFragment.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments;
+
+import static com.cyanogenmod.eleven.utils.MusicUtils.mService;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.MediaStore;
+import android.support.v4.app.Fragment;
+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.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.MusicPlaybackService;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.SongAdapter;
+import com.cyanogenmod.eleven.dragdrop.DragSortListView;
+import com.cyanogenmod.eleven.dragdrop.DragSortListView.DragScrollProfile;
+import com.cyanogenmod.eleven.dragdrop.DragSortListView.DropListener;
+import com.cyanogenmod.eleven.dragdrop.DragSortListView.RemoveListener;
+import com.cyanogenmod.eleven.loaders.NowPlayingCursor;
+import com.cyanogenmod.eleven.loaders.QueueLoader;
+import com.cyanogenmod.eleven.menu.DeleteDialog;
+import com.cyanogenmod.eleven.menu.FragmentMenuItems;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.recycler.RecycleHolder;
+import com.cyanogenmod.eleven.service.MusicPlaybackTrack;
+import com.cyanogenmod.eleven.ui.activities.SlidingPanelActivity;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.widgets.IPopupMenuCallback;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+import com.cyanogenmod.eleven.widgets.NoResultsContainer;
+import com.cyanogenmod.eleven.widgets.PlayPauseProgressButton;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.TreeSet;
+
+/**
+ * This class is used to display all of the songs in the queue.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class QueueFragment extends Fragment implements LoaderCallbacks<List<Song>>,
+ OnItemClickListener, DropListener, RemoveListener, DragScrollProfile, ServiceConnection {
+
+ /**
+ * LoaderCallbacks identifier
+ */
+ private static final int LOADER = 0;
+
+ /**
+ * Service token for binding to the music service
+ */
+ private MusicUtils.ServiceToken mToken;
+
+ /**
+ * The listener to the playback service that will trigger updates to the ui
+ */
+ private QueueUpdateListener mQueueUpdateListener;
+
+ /**
+ * The adapter for the list
+ */
+ private SongAdapter mAdapter;
+
+ /**
+ * The list view
+ */
+ private DragSortListView mListView;
+
+ /**
+ * Pop up menu helper
+ */
+ private PopupMenuHelper mPopupMenuHelper;
+
+ /**
+ * Root view
+ */
+ private ViewGroup mRootView;
+
+ /**
+ * This holds the loading progress bar as well as the no results message
+ */
+ private LoadingEmptyContainer mLoadingEmptyContainer;
+
+ /**
+ * Empty constructor as per the {@link Fragment} documentation
+ */
+ public QueueFragment() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mPopupMenuHelper = new PopupMenuHelper(getActivity(), getFragmentManager()) {
+ private Song mSong;
+ private int mSelectedPosition;
+ private MusicPlaybackTrack mSelectedTrack;
+
+ @Override
+ public PopupMenuType onPreparePopupMenu(int position) {
+ mSelectedPosition = position;
+ mSong = mAdapter.getItem(mSelectedPosition);
+ mSelectedTrack = MusicUtils.getTrack(mSelectedPosition);
+
+ return PopupMenuType.Queue;
+ }
+
+ @Override
+ protected long[] getIdList() {
+ return new long[] { mSong.mSongId };
+ }
+
+ @Override
+ protected long getSourceId() {
+ if (mSelectedTrack == null) {
+ return -1;
+ }
+
+ return mSelectedTrack.mSourceId;
+ }
+
+ @Override
+ protected Config.IdType getSourceType() {
+ if (mSelectedTrack == null) {
+ return Config.IdType.NA;
+ }
+
+ return mSelectedTrack.mSourceType;
+ }
+
+ @Override
+ protected String getArtistName() {
+ return mSong.mArtistName;
+ }
+
+ @Override
+ protected void onDeleteClicked() {
+ DeleteDialog.newInstance(mSong.mSongName,
+ new long[] { getId() }, null).show(getFragmentManager(), "DeleteDialog");
+ }
+
+ @Override
+ protected void playNext() {
+ NowPlayingCursor queue = (NowPlayingCursor)QueueLoader
+ .makeQueueCursor(getActivity());
+ queue.removeItem(mSelectedPosition);
+ queue.close();
+ queue = null;
+ MusicUtils.playNext(getIdList(), getSourceId(), getSourceType());
+ refreshQueue();
+ }
+
+ @Override
+ protected void removeFromQueue() {
+ MusicUtils.removeTrackAtPosition(getId(), mSelectedPosition);
+ refreshQueue();
+ }
+
+ @Override
+ protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
+ super.updateMenuIds(type, set);
+
+ // Don't show more by artist if it is an unknown artist
+ if (MediaStore.UNKNOWN_STRING.equals(mSong.mArtistName)) {
+ set.remove(FragmentMenuItems.MORE_BY_ARTIST);
+ }
+ }
+ };
+
+ // Create the adapter
+ mAdapter = new SongAdapter(getActivity(), R.layout.edit_queue_list_item,
+ -1, Config.IdType.NA);
+ mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mPopupMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ // The View for the fragment's UI
+ mRootView = (ViewGroup)inflater.inflate(R.layout.list_base, null);
+ // Initialize the list
+ mListView = (DragSortListView)mRootView.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());
+ // Play the selected song
+ mListView.setOnItemClickListener(this);
+ // Set the drop listener
+ mListView.setDropListener(this);
+ // Set the swipe to remove listener
+ mListView.setRemoveListener(this);
+ // Quick scroll while dragging
+ mListView.setDragScrollProfile(this);
+ // Enable fast scroll bars
+ mListView.setFastScrollEnabled(true);
+ // Setup the loading and empty state
+ mLoadingEmptyContainer =
+ (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
+ // Setup the container strings
+ setupNoResultsContainer(mLoadingEmptyContainer.getNoResultsContainer());
+ mListView.setEmptyView(mLoadingEmptyContainer);
+ return mRootView;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ // Initialize the broadcast receiver
+ mQueueUpdateListener = new QueueUpdateListener(this);
+
+ // Bind Apollo's service
+ mToken = MusicUtils.bindToService(getActivity(), this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onServiceConnected(final ComponentName name, final IBinder service) {
+ refreshQueue();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ final IntentFilter filter = new IntentFilter();
+ // Play and pause changes
+ filter.addAction(MusicPlaybackService.PLAYSTATE_CHANGED);
+ // Queue changes
+ filter.addAction(MusicPlaybackService.QUEUE_CHANGED);
+ // Track changes
+ filter.addAction(MusicPlaybackService.META_CHANGED);
+
+ getActivity().registerReceiver(mQueueUpdateListener, filter);
+
+ // resume the progress listeners
+ setPlayPauseProgressButtonStates(false);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+
+ // stops the progress listeners
+ setPlayPauseProgressButtonStates(true);
+ }
+
+ /**
+ * Sets the state for any play pause progress buttons under the listview
+ * This is neede because the buttons update themselves so if the activity
+ * is hidden, we want to pause those handlers
+ * @param pause the state to set it to
+ */
+ public void setPlayPauseProgressButtonStates(boolean pause) {
+ if (mListView != null) {
+ // walk through the visible list items
+ for (int i = mListView.getFirstVisiblePosition();
+ i <= mListView.getLastVisiblePosition(); i++) {
+ View childView = mListView.getChildAt(i);
+ if (childView != null) {
+ PlayPauseProgressButton button =
+ (PlayPauseProgressButton) childView.findViewById(R.id.playPauseProgressButton);
+ // pause or resume based on the flag
+ if (pause) {
+ button.pause();
+ } else {
+ button.resume();
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ try {
+ getActivity().unregisterReceiver(mQueueUpdateListener);
+ } catch (final Throwable e) {
+ //$FALL-THROUGH$
+ }
+
+ if (mService != null) {
+ MusicUtils.unbindFromService(mToken);
+ mToken = null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onItemClick(final AdapterView<?> parent, final View view, final int position,
+ final long id) {
+ // When selecting a track from the queue, just jump there instead of
+ // reloading the queue. This is both faster, and prevents accidentally
+ // dropping out of party shuffle.
+ MusicUtils.setQueuePosition(position);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<List<Song>> onCreateLoader(final int id, final Bundle args) {
+ mLoadingEmptyContainer.showLoading();
+ return new QueueLoader(getActivity());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<List<Song>> loader, final List<Song> data) {
+ // pause notifying the adapter and make changes before re-enabling it so that the list
+ // view doesn't reset to the top of the list
+ mAdapter.setNotifyOnChange(false);
+ mAdapter.unload(); // Start fresh
+
+ if (data.isEmpty()) {
+ mLoadingEmptyContainer.showNoResults();
+ mAdapter.setCurrentQueuePosition(SongAdapter.NOTHING_PLAYING);
+ ((SlidingPanelActivity)getActivity()).clearMetaInfo();
+ } else {
+ // Add the songs found to the adapter
+ for (final Song song : data) { mAdapter.add(song); }
+ // Build the cache
+ mAdapter.buildCache();
+ // Set the currently playing audio
+ mAdapter.setCurrentQueuePosition(MusicUtils.getQueuePosition());
+ }
+ // re-enable the notify by calling notify dataset changes
+ mAdapter.notifyDataSetChanged();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<List<Song>> loader) {
+ // Clear the data in the adapter
+ mAdapter.unload();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public float getSpeed(final float w, final long t) {
+ if (w > 0.8f) {
+ return mAdapter.getCount() / 0.001f;
+ } else {
+ return 10.0f * w;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void remove(final int which) {
+ Song song = mAdapter.getItem(which);
+ mAdapter.remove(song);
+ mAdapter.notifyDataSetChanged();
+ MusicUtils.removeTrackAtPosition(song.mSongId, which);
+ // Build the cache
+ mAdapter.buildCache();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void drop(final int from, final int to) {
+ Song song = mAdapter.getItem(from);
+ mAdapter.remove(song);
+ mAdapter.insert(song, to);
+ mAdapter.notifyDataSetChanged();
+ MusicUtils.moveQueueItem(from, to);
+ // Build the cache
+ mAdapter.buildCache();
+ }
+
+ /**
+ * Called to restart the loader callbacks
+ */
+ public void refreshQueue() {
+ if (isAdded()) {
+ getLoaderManager().restartLoader(LOADER, null, this);
+ }
+ }
+
+ private void setupNoResultsContainer(NoResultsContainer empty) {
+ int color = getResources().getColor(R.color.no_results_light);
+ empty.setTextColor(color);
+ empty.setMainText(R.string.empty_queue_main);
+ empty.setSecondaryText(R.string.empty_queue_secondary);
+ }
+
+ /**
+ * Used to monitor the state of playback
+ */
+ private static final class QueueUpdateListener extends BroadcastReceiver {
+
+ private final WeakReference<QueueFragment> mReference;
+
+ /**
+ * Constructor of <code>PlaybackStatus</code>
+ */
+ public QueueUpdateListener(final QueueFragment fragment) {
+ mReference = new WeakReference<QueueFragment>(fragment);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ // TODO: Invalid options menu if opened?
+ final String action = intent.getAction();
+ if (action.equals(MusicPlaybackService.META_CHANGED)) {
+ mReference.get().mAdapter.setCurrentQueuePosition(MusicUtils.getQueuePosition());
+ } else if (action.equals(MusicPlaybackService.PLAYSTATE_CHANGED)) {
+ mReference.get().mAdapter.notifyDataSetChanged();
+ } else if (action.equals(MusicPlaybackService.QUEUE_CHANGED)) {
+ mReference.get().refreshQueue();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/org/lineageos/eleven/ui/fragments/RecentFragment.java b/src/org/lineageos/eleven/ui/fragments/RecentFragment.java
new file mode 100644
index 0000000..971f8aa
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/RecentFragment.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.Config.SmartPlaylistType;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.SongAdapter;
+import com.cyanogenmod.eleven.loaders.TopTracksLoader;
+import com.cyanogenmod.eleven.menu.FragmentMenuItems;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.sectionadapter.SectionCreator;
+import com.cyanogenmod.eleven.sectionadapter.SectionListContainer;
+import com.cyanogenmod.eleven.ui.activities.BaseActivity;
+import com.cyanogenmod.eleven.ui.fragments.profile.SmartPlaylistFragment;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.widgets.NoResultsContainer;
+
+import java.util.TreeSet;
+
+/**
+ * This class is used to display all of the recently listened to songs by the
+ * user.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class RecentFragment extends SmartPlaylistFragment implements ISetupActionBar {
+
+ @Override
+ protected SmartPlaylistType getSmartPlaylistType() {
+ return Config.SmartPlaylistType.RecentlyPlayed;
+ }
+
+ @Override
+ protected void updateMenuIds(TreeSet<Integer> set) {
+ set.add(FragmentMenuItems.REMOVE_FROM_RECENT);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<SectionListContainer<Song>> onCreateLoader(final int id, final Bundle args) {
+ // show the loading progress bar
+ mLoadingEmptyContainer.showLoading();
+
+ TopTracksLoader loader = new TopTracksLoader(getActivity(),
+ TopTracksLoader.QueryType.RecentSongs);
+ return new SectionCreator<Song>(getActivity(), loader, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onMetaChanged() {
+ super.onMetaChanged();
+
+ // refresh the list since a track playing means it should be recently played
+ restartLoader();
+ }
+
+ @Override
+ public void setupNoResultsContainer(NoResultsContainer empty) {
+ super.setupNoResultsContainer(empty);
+
+ empty.setMainText(R.string.empty_recent_main);
+ empty.setSecondaryText(R.string.empty_recent);
+ }
+
+ @Override
+ public final View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ setupActionBar();
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ @Override
+ public void setupActionBar() {
+ ((BaseActivity)getActivity()).setupActionBar(R.string.playlist_recently_played);
+ ((BaseActivity)getActivity()).setActionBarElevation(true);
+ }
+
+ @Override
+ protected long getFragmentSourceId() {
+ return Config.SmartPlaylistType.RecentlyPlayed.mId;
+ }
+
+ @Override
+ protected SongAdapter createAdapter() {
+ return new RecentAdapter(
+ getActivity(),
+ R.layout.list_item_normal,
+ getFragmentSourceId(),
+ getFragmentSourceType()
+ );
+ }
+
+ private class RecentAdapter extends SongAdapter {
+ public RecentAdapter(Activity context, int layoutId, long sourceId, Config.IdType sourceType) {
+ super(context, layoutId, sourceId, sourceType);
+ }
+
+ @Override
+ protected boolean showNowPlayingIndicator(Song song, int position) {
+ return position == 0 && super.showNowPlayingIndicator(song, position);
+ }
+ }
+
+ @Override
+ protected int getShuffleTitleId() { return R.string.menu_shuffle_recent; }
+
+ @Override
+ protected int getClearTitleId() { return R.string.clear_recent_title; }
+
+ @Override
+ protected void clearList() { MusicUtils.clearRecent(getActivity()); }
+} \ No newline at end of file
diff --git a/src/org/lineageos/eleven/ui/fragments/SongFragment.java b/src/org/lineageos/eleven/ui/fragments/SongFragment.java
new file mode 100644
index 0000000..1e57580
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/SongFragment.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.loaders.SongLoader;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.sectionadapter.SectionCreator;
+import com.cyanogenmod.eleven.sectionadapter.SectionListContainer;
+import com.cyanogenmod.eleven.ui.fragments.profile.BasicSongFragment;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.SectionCreatorUtils;
+
+/**
+ * This class is used to display all of the songs on a user's device.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class SongFragment extends BasicSongFragment {
+
+ /**
+ * {@inheritDoc}
+ */
+ public void playAll(int position) {
+ int internalPosition = mAdapter.getInternalPosition(position);
+ final long[] list = mAdapter.getUnderlyingAdapter().getSongIds();
+ if (list != null) {
+ MusicUtils.playAll(getActivity(), list, internalPosition, -1, Config.IdType.NA, false);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<SectionListContainer<Song>> onCreateLoader(final int id, final Bundle args) {
+ // show the loading progress bar
+ mLoadingEmptyContainer.showLoading();
+
+ // get the context
+ Context context = getActivity();
+
+ // create the underlying song loader
+ SongLoader songLoader = new SongLoader(context);
+
+ // get the song comparison method to create the headers with
+ SectionCreatorUtils.IItemCompare<Song> songComparison = SectionCreatorUtils.createSongComparison(context);
+
+ // return the wrapped section creator
+ return new SectionCreator<Song>(context, songLoader, songComparison);
+ }
+
+
+ @Override
+ public int getLoaderId() {
+ return PagerAdapter.MusicFragments.SONG.ordinal();
+ }
+
+ /**
+ * Scrolls the list to the currently playing song when the user touches the
+ * header in the {@link TitlePageIndicator}.
+ */
+ public void scrollToCurrentSong() {
+ final int currentSongPosition = getItemPositionBySong();
+
+ if (currentSongPosition != 0) {
+ mListView.setSelection(currentSongPosition);
+ }
+ }
+
+ /**
+ * @return The position of an item in the list based on the name of the
+ * currently playing song.
+ */
+ private int getItemPositionBySong() {
+ final long trackId = MusicUtils.getCurrentAudioId();
+ if (mAdapter == null) {
+ return 0;
+ }
+
+ int position = mAdapter.getItemPosition(trackId);
+
+ // if for some reason we don't find the item, just jump to the top
+ if (position < 0) {
+ return 0;
+ }
+
+ return position;
+ }
+
+ @Override
+ public LoaderManager getFragmentLoaderManager() {
+ return getParentFragment().getLoaderManager();
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/phone/MusicBrowserFragment.java b/src/org/lineageos/eleven/ui/fragments/phone/MusicBrowserFragment.java
new file mode 100644
index 0000000..0d01e5f
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/phone/MusicBrowserFragment.java
@@ -0,0 +1,43 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments.phone;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+
+/**
+ * This class is used for fragments under the {@link MusicBrowserFragment}
+ * Even though the containing view pager creates all the fragments, the loader
+ * does not load complete until the user navigates to that page. To get around this
+ * we will use the containing fragment's loader manager
+ */
+public abstract class MusicBrowserFragment extends Fragment {
+ public abstract int getLoaderId();
+
+ public LoaderManager getContainingLoaderManager() {
+ return getParentFragment().getLoaderManager();
+ }
+
+ protected void initLoader(Bundle args, LoaderCallbacks<? extends Object> callback) {
+ getContainingLoaderManager().initLoader(getLoaderId(), args, callback);
+ }
+
+ protected void restartLoader(Bundle args, LoaderCallbacks<? extends Object> callback) {
+ getContainingLoaderManager().restartLoader(getLoaderId(), args, callback);
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/phone/MusicBrowserPhoneFragment.java b/src/org/lineageos/eleven/ui/fragments/phone/MusicBrowserPhoneFragment.java
new file mode 100644
index 0000000..376841e
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/phone/MusicBrowserPhoneFragment.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments.phone;
+
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.view.ViewPager;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.adapters.PagerAdapter.MusicFragments;
+import com.cyanogenmod.eleven.menu.CreateNewPlaylist;
+import com.cyanogenmod.eleven.ui.fragments.AlbumFragment;
+import com.cyanogenmod.eleven.ui.fragments.ArtistFragment;
+import com.cyanogenmod.eleven.ui.fragments.BaseFragment;
+import com.cyanogenmod.eleven.ui.fragments.SongFragment;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.PreferenceUtils;
+import com.cyanogenmod.eleven.utils.SortOrder;
+import com.cyanogenmod.eleven.widgets.ViewPagerTabs;
+
+/**
+ * This class is used to hold the {@link ViewPager} used for swiping between the
+ * playlists, recent, artists, albums, songs, and genre {@link Fragment}
+ * s for phones.
+ *
+ * @NOTE: The reason the sort orders are taken care of in this fragment rather
+ * than the individual fragments is to keep from showing all of the menu
+ * items on tablet interfaces. That being said, I have a tablet interface
+ * worked out, but I'm going to keep it in the Play Store version of
+ * Apollo for a couple of weeks or so before merging it with CM.
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class MusicBrowserPhoneFragment extends BaseFragment {
+ public static final int INVALID_PAGE_INDEX = -1;
+
+ /**
+ * Pager
+ */
+ private ViewPager mViewPager;
+
+ /**
+ * VP's adapter
+ */
+ private PagerAdapter mPagerAdapter;
+
+ private PreferenceUtils mPreferences;
+
+ /**
+ * A pre-defined page index to navigate to
+ */
+ private int mDefaultPageIdx = INVALID_PAGE_INDEX;
+
+ /**
+ * Empty constructor as per the {@link Fragment} documentation
+ */
+ public MusicBrowserPhoneFragment() {
+ }
+
+ @Override
+ protected int getLayoutToInflate() {
+ return R.layout.fragment_music_browser_phone;
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.app_name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Get the preferences
+ mPreferences = PreferenceUtils.getInstance(getActivity());
+ }
+
+ @Override
+ protected void onViewCreated() {
+ super.onViewCreated();
+
+ // create the adapter - on rotation the view gets created again and we need to recreate
+ // the child fragments (fragments of fragments cannot be retained)
+ mPagerAdapter = new PagerAdapter(getActivity(), getChildFragmentManager());
+ final MusicFragments[] mFragments = MusicFragments.values();
+ for (final MusicFragments mFragment : mFragments) {
+ mPagerAdapter.add(mFragment.getFragmentClass(), null);
+ }
+
+ // Initialize the ViewPager
+ mViewPager = (ViewPager)mRootView.findViewById(R.id.fragment_home_phone_pager);
+ // Attch the adapter
+ mViewPager.setAdapter(mPagerAdapter);
+ // Offscreen pager loading limit
+ mViewPager.setOffscreenPageLimit(mPagerAdapter.getCount() - 1);
+
+ // Initialize the tab strip
+ final ViewPagerTabs tabs = (ViewPagerTabs)
+ mRootView.findViewById(R.id.fragment_home_phone_pager_titles);
+ // Attach the ViewPager
+ tabs.setViewPager(mViewPager);
+ mViewPager.setOnPageChangeListener(tabs);
+
+ if (mDefaultPageIdx != INVALID_PAGE_INDEX) {
+ navigateToPage(mDefaultPageIdx);
+ } else {
+ // Start on the last page the user was on
+ navigateToPage(mPreferences.getStartPage());
+ }
+ }
+
+ public void setDefaultPageIdx(final int pageIdx) {
+ mDefaultPageIdx = pageIdx;
+ navigateToPage(mDefaultPageIdx);
+ }
+
+ private void navigateToPage(final int idx) {
+ // this may be called before the view is created, so do a check for mViewPager
+ if (idx != INVALID_PAGE_INDEX && mViewPager != null) {
+ mViewPager.setCurrentItem(idx);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Enable the options menu
+ setHasOptionsMenu(true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPause() {
+ super.onPause();
+ // Save the last page the use was on
+ mPreferences.setStartPage(mViewPager.getCurrentItem());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onPrepareOptionsMenu(final Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+
+ inflater.inflate(R.menu.shuffle_all, menu); // Shuffle all
+ if (isArtistPage()) {
+ inflater.inflate(R.menu.artist_sort_by, menu);
+ } else if (isAlbumPage()) {
+ inflater.inflate(R.menu.album_sort_by, menu);
+ } else if (isSongPage()) {
+ inflater.inflate(R.menu.song_sort_by, menu);
+ } else if (isPlaylistPage()) {
+ inflater.inflate(R.menu.new_playlist, menu);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_shuffle_all:
+ // Shuffle all the songs
+ MusicUtils.shuffleAll(getActivity());
+ return true;
+ case R.id.menu_sort_by_az:
+ if (isArtistPage()) {
+ mPreferences.setArtistSortOrder(SortOrder.ArtistSortOrder.ARTIST_A_Z);
+ getArtistFragment().refresh();
+ } else if (isAlbumPage()) {
+ mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_A_Z);
+ getAlbumFragment().refresh();
+ } else if (isSongPage()) {
+ mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_A_Z);
+ getSongFragment().refresh();
+ }
+ return true;
+ case R.id.menu_sort_by_za:
+ if (isArtistPage()) {
+ mPreferences.setArtistSortOrder(SortOrder.ArtistSortOrder.ARTIST_Z_A);
+ getArtistFragment().refresh();
+ } else if (isAlbumPage()) {
+ mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_Z_A);
+ getAlbumFragment().refresh();
+ } else if (isSongPage()) {
+ mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_Z_A);
+ getSongFragment().refresh();
+ }
+ return true;
+ case R.id.menu_sort_by_artist:
+ if (isAlbumPage()) {
+ mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_ARTIST);
+ getAlbumFragment().refresh();
+ } else if (isSongPage()) {
+ mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_ARTIST);
+ getSongFragment().refresh();
+ }
+ return true;
+ case R.id.menu_sort_by_album:
+ if (isSongPage()) {
+ mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_ALBUM);
+ getSongFragment().refresh();
+ }
+ return true;
+ case R.id.menu_sort_by_year:
+ if (isAlbumPage()) {
+ mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_YEAR);
+ getAlbumFragment().refresh();
+ } else if (isSongPage()) {
+ mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_YEAR);
+ getSongFragment().refresh();
+ }
+ return true;
+ case R.id.menu_sort_by_duration:
+ if (isSongPage()) {
+ mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_DURATION);
+ getSongFragment().refresh();
+ }
+ return true;
+ case R.id.menu_sort_by_number_of_songs:
+ if (isArtistPage()) {
+ mPreferences
+ .setArtistSortOrder(SortOrder.ArtistSortOrder.ARTIST_NUMBER_OF_SONGS);
+ getArtistFragment().refresh();
+ } else if (isAlbumPage()) {
+ mPreferences.setAlbumSortOrder(SortOrder.AlbumSortOrder.ALBUM_NUMBER_OF_SONGS);
+ getAlbumFragment().refresh();
+ }
+ return true;
+ case R.id.menu_sort_by_number_of_albums:
+ if (isArtistPage()) {
+ mPreferences
+ .setArtistSortOrder(SortOrder.ArtistSortOrder.ARTIST_NUMBER_OF_ALBUMS);
+ getArtistFragment().refresh();
+ }
+ return true;
+ case R.id.menu_sort_by_filename:
+ if(isSongPage()) {
+ mPreferences.setSongSortOrder(SortOrder.SongSortOrder.SONG_FILENAME);
+ getSongFragment().refresh();
+ }
+ return true;
+ case R.id.menu_new_playlist:
+ if(isPlaylistPage()) {
+ CreateNewPlaylist.getInstance(new long[0]).show(getFragmentManager(), "CreatePlaylist");
+ }
+ return true;
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected boolean needsElevatedActionBar() {
+ // our view pager already has elevation
+ return false;
+ }
+
+ private boolean isArtistPage() {
+ return mViewPager.getCurrentItem() == MusicFragments.ARTIST.ordinal();
+ }
+
+ public ArtistFragment getArtistFragment() {
+ return (ArtistFragment)mPagerAdapter.getFragment(MusicFragments.ARTIST.ordinal());
+ }
+
+ private boolean isAlbumPage() {
+ return mViewPager.getCurrentItem() == MusicFragments.ALBUM.ordinal();
+ }
+
+ public AlbumFragment getAlbumFragment() {
+ return (AlbumFragment)mPagerAdapter.getFragment(MusicFragments.ALBUM.ordinal());
+ }
+
+ private boolean isSongPage() {
+ return mViewPager.getCurrentItem() == MusicFragments.SONG.ordinal();
+ }
+
+ public SongFragment getSongFragment() {
+ return (SongFragment)mPagerAdapter.getFragment(MusicFragments.SONG.ordinal());
+ }
+
+ @Override
+ public void restartLoader() {
+ // do nothing
+ }
+
+ private boolean isPlaylistPage() {
+ return mViewPager.getCurrentItem() == MusicFragments.PLAYLIST.ordinal();
+ }
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/profile/BasicSongFragment.java b/src/org/lineageos/eleven/ui/fragments/profile/BasicSongFragment.java
new file mode 100644
index 0000000..ba9d815
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/profile/BasicSongFragment.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments.profile;
+
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+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.AbsListView;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.MusicStateListener;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.SongAdapter;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.recycler.RecycleHolder;
+import com.cyanogenmod.eleven.sectionadapter.SectionAdapter;
+import com.cyanogenmod.eleven.sectionadapter.SectionListContainer;
+import com.cyanogenmod.eleven.service.MusicPlaybackTrack;
+import com.cyanogenmod.eleven.ui.activities.BaseActivity;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.utils.SongPopupMenuHelper;
+import com.cyanogenmod.eleven.widgets.IPopupMenuCallback;
+import com.cyanogenmod.eleven.widgets.LoadingEmptyContainer;
+import com.cyanogenmod.eleven.widgets.NoResultsContainer;
+
+import java.util.TreeSet;
+
+/**
+ * This class is used to display all of the songs
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public abstract class BasicSongFragment extends Fragment implements
+ LoaderCallbacks<SectionListContainer<Song>>, OnItemClickListener, MusicStateListener {
+
+ /**
+ * Fragment UI
+ */
+ protected ViewGroup mRootView;
+
+ /**
+ * The adapter for the list
+ */
+ protected SectionAdapter<Song, SongAdapter> mAdapter;
+
+ /**
+ * The list view
+ */
+ protected ListView mListView;
+
+ /**
+ * Pop up menu helper
+ */
+ protected PopupMenuHelper mPopupMenuHelper;
+
+ /**
+ * This holds the loading progress bar as well as the no results message
+ */
+ protected LoadingEmptyContainer mLoadingEmptyContainer;
+
+ /**
+ * Empty constructor as per the {@link Fragment} documentation
+ */
+ public BasicSongFragment() {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mPopupMenuHelper = new SongPopupMenuHelper(getActivity(), getFragmentManager()) {
+ @Override
+ public Song getSong(int position) {
+ return mAdapter.getTItem(position);
+ }
+
+ @Override
+ protected long getSourceId() {
+ return getFragmentSourceId();
+ }
+
+ @Override
+ protected Config.IdType getSourceType() {
+ return getFragmentSourceType();
+ }
+
+ @Override
+ protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
+ super.updateMenuIds(type, set);
+ BasicSongFragment.this.updateMenuIds(set);
+ }
+ };
+
+ // Create the adapter
+ mAdapter = new SectionAdapter<Song, SongAdapter>(getActivity(), createAdapter());
+ mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
+ @Override
+ public void onPopupMenuClicked(View v, int position) {
+ mPopupMenuHelper.showPopupMenu(v, position);
+ }
+ });
+ }
+
+ protected long getFragmentSourceId() {
+ return -1;
+ }
+
+ protected Config.IdType getFragmentSourceType() {
+ return Config.IdType.NA;
+ }
+
+ protected void updateMenuIds(TreeSet<Integer> set) {
+ // do nothing - let subclasses override
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+ final Bundle savedInstanceState) {
+ // The View for the fragment's UI
+ mRootView = (ViewGroup) inflater.inflate(R.layout.list_base, null);
+ // set the background on the root view
+ mRootView.setBackgroundColor(getResources().getColor(R.color.background_color));
+ // Initialize the list
+ mListView = (ListView) mRootView.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());
+ // Play the selected song
+ mListView.setOnItemClickListener(this);
+ // To help make scrolling smooth
+ mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // Pause disk cache access to ensure smoother scrolling
+ if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
+ mAdapter.getUnderlyingAdapter().setPauseDiskCache(true);
+ } else {
+ mAdapter.getUnderlyingAdapter().setPauseDiskCache(false);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
+
+ }
+ });
+
+ // Show progress bar
+ mLoadingEmptyContainer = (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
+ // Setup the container strings
+ setupNoResultsContainer(mLoadingEmptyContainer.getNoResultsContainer());
+ mListView.setEmptyView(mLoadingEmptyContainer);
+
+ // Register the music status listener
+ ((BaseActivity)getActivity()).setMusicStateListenerListener(this);
+
+ return mRootView;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+
+ ((BaseActivity)getActivity()).removeMusicStateListenerListener(this);
+ }
+
+ /**
+ * This allows subclasses to customize the look and feel of the no results container
+ * @param empty NoResultsContainer class
+ */
+ public void setupNoResultsContainer(final NoResultsContainer empty) {
+ // do nothing
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onActivityCreated(final Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ // Start the loader
+ getFragmentLoaderManager().initLoader(getLoaderId(), null, this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onItemClick(final AdapterView<?> parent, final View view, final int position,
+ final long id) {
+ playAll(position);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoadFinished(final Loader<SectionListContainer<Song>> loader,
+ final SectionListContainer<Song> data) {
+ if (data.mListResults.isEmpty()) {
+ mAdapter.unload();
+ mLoadingEmptyContainer.showNoResults();
+ return;
+ }
+
+ mAdapter.setData(data);
+ }
+
+ /**
+ * @return Gets the list of song ids from the adapter, or null if none
+ */
+ protected long[] getSongIdsFromAdapter() {
+ if (mAdapter != null) {
+ final SongAdapter adapter = mAdapter.getUnderlyingAdapter();
+ if (adapter != null) {
+ return adapter.getSongIds();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Restarts the loader.
+ */
+ public void refresh() {
+ // Wait a moment for the preference to change.
+ SystemClock.sleep(10);
+ restartLoader();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void restartLoader() {
+ // Update the list when the user deletes any items
+ getFragmentLoaderManager().restartLoader(getLoaderId(), null, this);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onLoaderReset(final Loader<SectionListContainer<Song>> loader) {
+ // Clear the data in the adapter
+ mAdapter.unload();
+ }
+
+ /**
+ * If the subclasses want to use a customized SongAdapter they can override this method
+ * @return the Song adapter
+ */
+ protected SongAdapter createAdapter() {
+ return new SongAdapter(
+ getActivity(),
+ R.layout.list_item_normal,
+ getFragmentSourceId(),
+ getFragmentSourceType()
+ );
+ }
+
+ /**
+ * Allow subclasses to specify a different loader manager
+ * @return Loader Manager to use
+ */
+ public LoaderManager getFragmentLoaderManager() {
+ return getLoaderManager();
+ }
+
+ @Override
+ public void onMetaChanged() {
+ MusicPlaybackTrack currentTrack = MusicUtils.getCurrentTrack();
+ if (mAdapter.getUnderlyingAdapter().setCurrentlyPlayingTrack(currentTrack)) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onPlaylistChanged() {
+ // Nothing to do
+ }
+
+ /**
+ * LoaderCallbacks identifier
+ */
+ public abstract int getLoaderId();
+
+ /**
+ * If the user clisk play all
+ *
+ * @param position the position of the item clicked or -1 if shuffle all
+ */
+ public abstract void playAll(int position);
+
+}
diff --git a/src/org/lineageos/eleven/ui/fragments/profile/LastAddedFragment.java b/src/org/lineageos/eleven/ui/fragments/profile/LastAddedFragment.java
new file mode 100644
index 0000000..87300d9
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/profile/LastAddedFragment.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments.profile;
+
+import android.os.Bundle;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.Config.SmartPlaylistType;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.loaders.LastAddedLoader;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.sectionadapter.SectionCreator;
+import com.cyanogenmod.eleven.sectionadapter.SectionListContainer;
+import com.cyanogenmod.eleven.ui.activities.BaseActivity;
+import com.cyanogenmod.eleven.ui.fragments.ISetupActionBar;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.widgets.NoResultsContainer;
+
+/**
+ * This class is used to display all of the songs the user put on their device
+ * within the last four weeks.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class LastAddedFragment extends SmartPlaylistFragment implements ISetupActionBar {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<SectionListContainer<Song>> onCreateLoader(final int id, final Bundle args) {
+ // show the loading progress bar
+ mLoadingEmptyContainer.showLoading();
+
+ LastAddedLoader loader = new LastAddedLoader(getActivity());
+ return new SectionCreator<Song>(getActivity(), loader, null);
+ }
+
+ @Override
+ public void setupNoResultsContainer(NoResultsContainer empty) {
+ super.setupNoResultsContainer(empty);
+
+ empty.setMainText(R.string.empty_last_added_main);
+ empty.setSecondaryText(R.string.empty_last_added);
+ }
+
+ @Override
+ public final View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ setupActionBar();
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ @Override
+ public void setupActionBar() {
+ ((BaseActivity)getActivity()).setupActionBar(R.string.playlist_last_added);
+ ((BaseActivity)getActivity()).setActionBarElevation(true);
+ }
+
+ @Override
+ protected long getFragmentSourceId() {
+ return Config.SmartPlaylistType.LastAdded.mId;
+ }
+
+ protected SmartPlaylistType getSmartPlaylistType() {
+ return Config.SmartPlaylistType.LastAdded;
+ }
+
+ @Override
+ protected int getShuffleTitleId() { return R.string.menu_shuffle_last_added; }
+
+ @Override
+ protected int getClearTitleId() { return R.string.clear_last_added; }
+
+ @Override
+ protected void clearList() { MusicUtils.clearLastAdded(getActivity()); }
+} \ No newline at end of file
diff --git a/src/org/lineageos/eleven/ui/fragments/profile/SmartPlaylistFragment.java b/src/org/lineageos/eleven/ui/fragments/profile/SmartPlaylistFragment.java
new file mode 100644
index 0000000..72b2b41
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/profile/SmartPlaylistFragment.java
@@ -0,0 +1,142 @@
+/*
+* Copyright (C) 2014 The CyanogenMod Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+package com.cyanogenmod.eleven.ui.fragments.profile;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.cyanogenmod.eleven.Config.SmartPlaylistType;
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.PagerAdapter;
+import com.cyanogenmod.eleven.menu.ConfirmDialog;
+import com.cyanogenmod.eleven.model.Playlist;
+import com.cyanogenmod.eleven.ui.fragments.IChildFragment;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.utils.PlaylistPopupMenuHelper;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper;
+import com.cyanogenmod.eleven.utils.PopupMenuHelper.PopupMenuType;
+
+public abstract class SmartPlaylistFragment extends BasicSongFragment
+ implements ConfirmDialog.ConfirmCallback, IChildFragment {
+ /**
+ * LoaderCallbacks identifier
+ */
+ private static final int LOADER = 0;
+ private static final int CLEAR_REQUEST = 1;
+ private PopupMenuHelper mActionMenuHelper;
+
+ @Override
+ public int getLoaderId() { return LOADER; }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ setHasOptionsMenu(true);
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ @Override
+ protected Config.IdType getFragmentSourceType() {
+ return Config.IdType.Playlist;
+ }
+
+ @Override
+ public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+ inflater.inflate(R.menu.shuffle_item, menu);
+ menu.findItem(R.id.menu_shuffle_item).setTitle(getShuffleTitleId());
+
+ // use the same popup menu to provide actions for smart playlist
+ // as is used in the PlaylistFragment
+ mActionMenuHelper = new PlaylistPopupMenuHelper(
+ getActivity(), getChildFragmentManager(), PopupMenuType.SmartPlaylist) {
+ public Playlist getPlaylist(int position) {
+ SmartPlaylistType type = getSmartPlaylistType();
+ return new Playlist(type.mId, getString(type.mTitleId), 0);
+ }
+ };
+ mActionMenuHelper.onPreparePopupMenu(0);
+ mActionMenuHelper.createPopupMenu(menu);
+
+ inflater.inflate(R.menu.clear_list, menu);
+ super.onCreateOptionsMenu(menu, inflater);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.menu_shuffle_item:
+ playAll(-1, true);
+ return true;
+ case R.id.clear_list:
+ ConfirmDialog.show(
+ this, CLEAR_REQUEST, getClearTitleId(), R.string.clear);
+ return true;
+ default:
+ if(mActionMenuHelper.onMenuItemClick(item)) { return true; }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void confirmOk(int requestCode) {
+ if(requestCode == CLEAR_REQUEST) {
+ mAdapter.unload();
+ clearList();
+ restartLoader();
+ }
+ }
+
+ @Override
+ public void playAll(int position) {
+ playAll(position, false);
+ }
+
+ public void playAll(int position, boolean shuffle) {
+ // we grab the song ids from the adapter instead of querying the cursor because the user
+ // expects what they see to be what they play. The counter argument of updating the list
+ // could be made, but refreshing the smart playlists so often will be annoying and
+ // confusing for the user so this is an intermediate compromise. An example is the top
+ // tracks list is based on the # of times you play a song, but near the beginning each
+ // song being played will change the list and the compromise is to update only when you
+ // enter the page.
+ long[] songIds = getSongIdsFromAdapter();
+ if (songIds != null) {
+ MusicUtils.playAll(getActivity(), songIds, position, getSmartPlaylistType().mId,
+ Config.IdType.Playlist, shuffle);
+ }
+ }
+
+ public PagerAdapter.MusicFragments getMusicFragmentParent() {
+ return PagerAdapter.MusicFragments.PLAYLIST;
+ }
+
+ protected abstract SmartPlaylistType getSmartPlaylistType();
+
+ /** text for menu item that shuffles items in this playlist */
+ protected abstract int getShuffleTitleId();
+
+ /** text for confirmation dialog that clears this playlist */
+ protected abstract int getClearTitleId();
+
+ /** action that clears this playlist */
+ protected abstract void clearList();
+} \ No newline at end of file
diff --git a/src/org/lineageos/eleven/ui/fragments/profile/TopTracksFragment.java b/src/org/lineageos/eleven/ui/fragments/profile/TopTracksFragment.java
new file mode 100644
index 0000000..1d71316
--- /dev/null
+++ b/src/org/lineageos/eleven/ui/fragments/profile/TopTracksFragment.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2012 Andrew Neal
+ * Copyright (C) 2014 The CyanogenMod Project
+ * Licensed under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+package com.cyanogenmod.eleven.ui.fragments.profile;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.content.Loader;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.cyanogenmod.eleven.Config;
+import com.cyanogenmod.eleven.Config.SmartPlaylistType;
+import com.cyanogenmod.eleven.R;
+import com.cyanogenmod.eleven.adapters.SongAdapter;
+import com.cyanogenmod.eleven.loaders.TopTracksLoader;
+import com.cyanogenmod.eleven.model.Song;
+import com.cyanogenmod.eleven.sectionadapter.SectionCreator;
+import com.cyanogenmod.eleven.sectionadapter.SectionListContainer;
+import com.cyanogenmod.eleven.ui.activities.BaseActivity;
+import com.cyanogenmod.eleven.ui.fragments.ISetupActionBar;
+import com.cyanogenmod.eleven.utils.MusicUtils;
+import com.cyanogenmod.eleven.widgets.NoResultsContainer;
+
+/**
+ * This class is used to display all of the songs the user put on their device
+ * within the last four weeks.
+ *
+ * @author Andrew Neal (andrewdneal@gmail.com)
+ */
+public class TopTracksFragment extends SmartPlaylistFragment
+implements ISetupActionBar {
+
+ @Override
+ protected SmartPlaylistType getSmartPlaylistType() {
+ return Config.SmartPlaylistType.TopTracks;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Loader<SectionListContainer<Song>> onCreateLoader(final int id, final Bundle args) {
+ // show the loading progress bar
+ mLoadingEmptyContainer.showLoading();
+
+ TopTracksLoader loader = new TopTracksLoader(getActivity(),
+ TopTracksLoader.QueryType.TopTracks);
+ return new SectionCreator<Song>(getActivity(), loader, null);
+ }
+
+ @Override
+ protected SongAdapter createAdapter() {
+ return new TopTracksAdapter(
+ getActivity(),
+ R.layout.list_item_top_tracks
+ );
+ }
+
+ @Override
+ public final View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ setupActionBar();
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
+ public void setupActionBar() {
+ ((BaseActivity)getActivity()).setupActionBar(R.string.playlist_top_tracks);
+ ((BaseActivity)getActivity()).setActionBarElevation(true);
+ }
+
+ public class TopTracksAdapter extends SongAdapter {
+ public TopTracksAdapter (final Activity context, final int layoutId) {
+ super(context, layoutId, getFragmentSourceId(), getFragmentSourceType());
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = super.getView(position, convertView, parent);
+ TextView positionText = (TextView) view.findViewById(R.id.position_number);
+ positionText.setText(String.valueOf(position + 1));
+ return view;
+ }
+ }
+
+ @Override
+ public void setupNoResultsContainer(NoResultsContainer empty) {
+ super.setupNoResultsContainer(empty);
+
+ empty.setMainText(R.string.empty_top_tracks_main);
+ empty.setSecondaryText(R.string.empty_top_tracks_secondary);
+ }
+
+ @Override
+ protected long getFragmentSourceId() {
+ return Config.SmartPlaylistType.TopTracks.mId;
+ }
+
+ protected int getShuffleTitleId() { return R.string.menu_shuffle_top_tracks; }
+
+ @Override
+ protected int getClearTitleId() { return R.string.clear_top_tracks_title; }
+
+ @Override
+ protected void clearList() { MusicUtils.clearTopTracks(getActivity()); }
+} \ No newline at end of file