2 * Copyright (C) 2012 Andrew Neal Licensed under the Apache License, Version 2.0
3 * (the "License"); you may not use this file except in compliance with the
4 * License. You may obtain a copy of the License at
5 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
6 * or agreed to in writing, software distributed under the License is
7 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
8 * KIND, either express or implied. See the License for the specific language
9 * governing permissions and limitations under the License.
12 package com.cyngn.eleven.ui.fragments;
14 import static com.cyngn.eleven.utils.MusicUtils.mService;
16 import android.content.BroadcastReceiver;
17 import android.content.ComponentName;
18 import android.content.Context;
19 import android.content.Intent;
20 import android.content.IntentFilter;
21 import android.content.ServiceConnection;
22 import android.os.Bundle;
23 import android.os.IBinder;
24 import android.support.v4.app.Fragment;
25 import android.support.v4.app.LoaderManager.LoaderCallbacks;
26 import android.support.v4.content.Loader;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.AdapterView;
31 import android.widget.AdapterView.OnItemClickListener;
33 import com.cyngn.eleven.MusicPlaybackService;
34 import com.cyngn.eleven.R;
35 import com.cyngn.eleven.adapters.SongAdapter;
36 import com.cyngn.eleven.dragdrop.DragSortListView;
37 import com.cyngn.eleven.dragdrop.DragSortListView.DragScrollProfile;
38 import com.cyngn.eleven.dragdrop.DragSortListView.DropListener;
39 import com.cyngn.eleven.dragdrop.DragSortListView.RemoveListener;
40 import com.cyngn.eleven.loaders.NowPlayingCursor;
41 import com.cyngn.eleven.loaders.QueueLoader;
42 import com.cyngn.eleven.menu.DeleteDialog;
43 import com.cyngn.eleven.model.Song;
44 import com.cyngn.eleven.recycler.RecycleHolder;
45 import com.cyngn.eleven.ui.activities.SlidingPanelActivity;
46 import com.cyngn.eleven.utils.MusicUtils;
47 import com.cyngn.eleven.utils.PopupMenuHelper;
48 import com.cyngn.eleven.widgets.IPopupMenuCallback;
49 import com.cyngn.eleven.widgets.NoResultsContainer;
50 import com.cyngn.eleven.widgets.PlayPauseProgressButton;
52 import java.lang.ref.WeakReference;
53 import java.util.List;
56 * This class is used to display all of the songs in the queue.
58 * @author Andrew Neal (andrewdneal@gmail.com)
60 public class QueueFragment extends Fragment implements LoaderCallbacks<List<Song>>,
61 OnItemClickListener, DropListener, RemoveListener, DragScrollProfile, ServiceConnection {
64 * LoaderCallbacks identifier
66 private static final int LOADER = 0;
69 * Service token for binding to the music service
71 private MusicUtils.ServiceToken mToken;
74 * The listener to the playback service that will trigger updates to the ui
76 private QueueUpdateListener mQueueUpdateListener;
79 * The adapter for the list
81 private SongAdapter mAdapter;
86 private DragSortListView mListView;
91 private PopupMenuHelper mPopupMenuHelper;
93 private ViewGroup mRootView;
96 * Empty constructor as per the {@link Fragment} documentation
98 public QueueFragment() {
105 public void onCreate(final Bundle savedInstanceState) {
106 super.onCreate(savedInstanceState);
107 mPopupMenuHelper = new PopupMenuHelper(getActivity(), getFragmentManager()) {
109 private int mSelectedPosition;
112 protected PopupMenuType onPreparePopupMenu(int position) {
113 mSelectedPosition = position;
114 mSong = mAdapter.getItem(mSelectedPosition);
116 return PopupMenuType.Queue;
120 protected long[] getIdList() {
121 return new long[] { mSong.mSongId };
125 protected String getArtistName() {
126 return mSong.mArtistName;
130 protected void onDeleteClicked() {
131 DeleteDialog.newInstance(mSong.mSongName,
132 new long[] { getId() }, null).show(getFragmentManager(), "DeleteDialog");
136 protected void playNext() {
137 NowPlayingCursor queue = (NowPlayingCursor)QueueLoader
138 .makeQueueCursor(getActivity());
139 queue.removeItem(mSelectedPosition);
142 MusicUtils.playNext(getIdList());
147 protected void removeFromQueue() {
148 MusicUtils.removeTrack(getId());
153 // Create the adapter
154 mAdapter = new SongAdapter(getActivity(), R.layout.edit_queue_list_item);
155 mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
157 public void onPopupMenuClicked(View v, int position) {
158 mPopupMenuHelper.showPopupMenu(v, position);
167 public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
168 final Bundle savedInstanceState) {
169 // The View for the fragment's UI
170 mRootView = (ViewGroup)inflater.inflate(R.layout.list_base, null);
171 // Initialize the list
172 mListView = (DragSortListView)mRootView.findViewById(R.id.list_base);
173 // Set the data behind the list
174 mListView.setAdapter(mAdapter);
175 // Release any references to the recycled Views
176 mListView.setRecyclerListener(new RecycleHolder());
177 // Play the selected song
178 mListView.setOnItemClickListener(this);
179 // Set the drop listener
180 mListView.setDropListener(this);
181 // Set the swipe to remove listener
182 mListView.setRemoveListener(this);
183 // Quick scroll while dragging
184 mListView.setDragScrollProfile(this);
185 // Enable fast scroll bars
186 mListView.setFastScrollEnabled(true);
194 public void onActivityCreated(final Bundle savedInstanceState) {
195 super.onActivityCreated(savedInstanceState);
197 // Initialize the broadcast receiver
198 mQueueUpdateListener = new QueueUpdateListener(this);
200 // Bind Apollo's service
201 mToken = MusicUtils.bindToService(getActivity(), this);
208 public void onServiceConnected(final ComponentName name, final IBinder service) {
213 public void onServiceDisconnected(ComponentName name) {
217 public void onStart() {
220 final IntentFilter filter = new IntentFilter();
221 // Play and pause changes
222 filter.addAction(MusicPlaybackService.PLAYSTATE_CHANGED);
224 filter.addAction(MusicPlaybackService.QUEUE_CHANGED);
226 filter.addAction(MusicPlaybackService.META_CHANGED);
228 getActivity().registerReceiver(mQueueUpdateListener, filter);
230 // resume the progress listeners
231 setPlayPauseProgressButtonStates(false);
235 public void onStop() {
238 // stops the progress listeners
239 setPlayPauseProgressButtonStates(true);
243 * Sets the state for any play pause progress buttons under the listview
244 * This is neede because the buttons update themselves so if the activity
245 * is hidden, we want to pause those handlers
246 * @param pause the state to set it to
248 public void setPlayPauseProgressButtonStates(boolean pause) {
249 if (mListView != null) {
250 // walk through the visible list items
251 for (int i = mListView.getFirstVisiblePosition();
252 i <= mListView.getLastVisiblePosition(); i++) {
253 View childView = mListView.getChildAt(i);
254 if (childView != null) {
255 PlayPauseProgressButton button = (PlayPauseProgressButton) childView.findViewById(R.id.playPauseProgressButton);
257 // pause or resume based on the flag
269 public void onDestroy() {
273 getActivity().unregisterReceiver(mQueueUpdateListener);
274 } catch (final Throwable e) {
278 if (mService != null) {
279 MusicUtils.unbindFromService(mToken);
288 public void onItemClick(final AdapterView<?> parent, final View view, final int position,
290 // When selecting a track from the queue, just jump there instead of
291 // reloading the queue. This is both faster, and prevents accidentally
292 // dropping out of party shuffle.
293 MusicUtils.setQueuePosition(position);
300 public Loader<List<Song>> onCreateLoader(final int id, final Bundle args) {
301 return new QueueLoader(getActivity());
308 public void onLoadFinished(final Loader<List<Song>> loader, final List<Song> data) {
309 mAdapter.unload(); // Start fresh
311 if (data.isEmpty()) {
312 // No songs found, bring up the empty view
313 final NoResultsContainer empty =
314 (NoResultsContainer)mRootView.findViewById(R.id.no_results_container);
315 // Setup the container strings
316 setupNoResultsContainer(empty);
317 // set the empty view into the list view
318 mListView.setEmptyView(empty);
319 mAdapter.setCurrentlyPlayingSongId(SongAdapter.NOTHING_PLAYING);
320 ((SlidingPanelActivity)getActivity()).clearMetaInfo();
322 // Add the songs found to the adapter
323 for (final Song song : data) { mAdapter.add(song); }
325 mAdapter.buildCache();
326 // Set the currently playing audio
327 mAdapter.setCurrentlyPlayingSongId(MusicUtils.getCurrentAudioId());
335 public void onLoaderReset(final Loader<List<Song>> loader) {
336 // Clear the data in the adapter
344 public float getSpeed(final float w, final long t) {
346 return mAdapter.getCount() / 0.001f;
356 public void remove(final int which) {
357 Song song = mAdapter.getItem(which);
358 mAdapter.remove(song);
359 mAdapter.notifyDataSetChanged();
360 MusicUtils.removeTrack(song.mSongId);
362 mAdapter.buildCache();
369 public void drop(final int from, final int to) {
370 Song song = mAdapter.getItem(from);
371 mAdapter.remove(song);
372 mAdapter.insert(song, to);
373 mAdapter.notifyDataSetChanged();
374 MusicUtils.moveQueueItem(from, to);
376 mAdapter.buildCache();
380 * Called to restart the loader callbacks
382 public void refreshQueue() {
384 getLoaderManager().restartLoader(LOADER, null, this);
388 private void setupNoResultsContainer(NoResultsContainer empty) {
389 int color = getResources().getColor(R.color.no_results_light);
390 empty.setTextColor(color);
391 empty.setMainText(R.string.empty_queue_main);
392 empty.setSecondaryText(R.string.empty_queue_secondary);
396 * Used to monitor the state of playback
398 private static final class QueueUpdateListener extends BroadcastReceiver {
400 private final WeakReference<QueueFragment> mReference;
403 * Constructor of <code>PlaybackStatus</code>
405 public QueueUpdateListener(final QueueFragment fragment) {
406 mReference = new WeakReference<QueueFragment>(fragment);
413 public void onReceive(final Context context, final Intent intent) {
414 // TODO: Invalid options menu if opened?
415 final String action = intent.getAction();
416 if (action.equals(MusicPlaybackService.META_CHANGED)) {
417 mReference.get().mAdapter.setCurrentlyPlayingSongId(MusicUtils.getCurrentAudioId());
418 } else if (action.equals(MusicPlaybackService.PLAYSTATE_CHANGED)) {
419 mReference.get().mAdapter.notifyDataSetChanged();
420 } else if (action.equals(MusicPlaybackService.QUEUE_CHANGED)) {
421 mReference.get().refreshQueue();