2 * Copyright (C) 2014 The CyanogenMod Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package org.lineageos.eleven.ui.fragments;
18 import android.database.Cursor;
19 import android.net.Uri;
20 import android.os.Bundle;
21 import android.provider.MediaStore;
22 import android.view.View;
23 import android.widget.AbsListView;
24 import android.widget.AdapterView;
25 import android.widget.AdapterView.OnItemClickListener;
26 import android.widget.ImageView;
27 import android.widget.TextView;
29 import androidx.loader.app.LoaderManager;
30 import androidx.loader.content.Loader;
32 import org.lineageos.eleven.Config;
33 import org.lineageos.eleven.R;
34 import org.lineageos.eleven.adapters.PagerAdapter;
35 import org.lineageos.eleven.adapters.ProfileSongAdapter;
36 import org.lineageos.eleven.cache.ImageFetcher;
37 import org.lineageos.eleven.dragdrop.DragSortListView;
38 import org.lineageos.eleven.dragdrop.DragSortListView.DragScrollProfile;
39 import org.lineageos.eleven.dragdrop.DragSortListView.DropListener;
40 import org.lineageos.eleven.dragdrop.DragSortListView.RemoveListener;
41 import org.lineageos.eleven.loaders.PlaylistSongLoader;
42 import org.lineageos.eleven.menu.FragmentMenuItems;
43 import org.lineageos.eleven.model.Playlist;
44 import org.lineageos.eleven.model.Song;
45 import org.lineageos.eleven.recycler.RecycleHolder;
46 import org.lineageos.eleven.utils.MusicUtils;
47 import org.lineageos.eleven.utils.PlaylistPopupMenuHelper;
48 import org.lineageos.eleven.utils.PopupMenuHelper;
49 import org.lineageos.eleven.utils.PopupMenuHelper.PopupMenuType;
50 import org.lineageos.eleven.utils.SongPopupMenuHelper;
51 import org.lineageos.eleven.widgets.IPopupMenuCallback;
52 import org.lineageos.eleven.widgets.LoadingEmptyContainer;
53 import org.lineageos.eleven.widgets.NoResultsContainer;
55 import java.util.List;
56 import java.util.TreeSet;
58 public class PlaylistDetailFragment extends FadingBarFragment implements
59 LoaderManager.LoaderCallbacks<List<Song>>, OnItemClickListener, DropListener,
60 RemoveListener, DragScrollProfile, IChildFragment {
63 * LoaderCallbacks identifier
65 private static final int LOADER = 0;
67 private DragSortListView mListView;
68 private ProfileSongAdapter mAdapter;
70 private View mHeaderContainer;
71 private ImageView mPlaylistImageView;
73 private LoadingEmptyContainer mLoadingEmptyContainer;
75 private TextView mNumberOfSongs;
76 private TextView mDurationOfPlaylist;
79 * The Id of the playlist the songs belong to
81 private long mPlaylistId;
82 private String mPlaylistName;
87 private PopupMenuHelper mPopupMenuHelper;
90 protected String getTitle() { return mPlaylistName; }
93 protected int getLayoutToInflate() {
94 return R.layout.playlist_detail;
98 protected void onViewCreated() {
99 super.onViewCreated();
104 private void lookupName() {
105 mPlaylistName = MusicUtils.getNameForPlaylist(getActivity(), mPlaylistId);
108 @Override // DetailFragment
109 protected PopupMenuHelper createActionMenuHelper() {
110 return new PlaylistPopupMenuHelper(
111 getActivity(), getChildFragmentManager(), PopupMenuType.Playlist) {
112 public Playlist getPlaylist(int position) {
113 return new Playlist(mPlaylistId, getTitle(), 0);
118 @Override // DetailFragment
119 protected int getShuffleTitleId() { return R.string.menu_shuffle_playlist; }
121 @Override // DetailFragment
122 protected void playShuffled() {
123 MusicUtils.playPlaylist(getActivity(), mPlaylistId, true);
127 public void onActivityCreated(Bundle savedInstanceState) {
128 super.onActivityCreated(savedInstanceState);
130 LoaderManager lm = getLoaderManager();
131 lm.initLoader(0, getArguments(), this);
135 public void onCreate(Bundle savedInstanceState) {
136 super.onCreate(savedInstanceState);
138 mPopupMenuHelper = new SongPopupMenuHelper(getActivity(), getFragmentManager()) {
140 public Song getSong(int position) {
145 return mAdapter.getItem(position);
149 protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
150 super.updateMenuIds(type, set);
152 set.add(FragmentMenuItems.REMOVE_FROM_PLAYLIST);
153 set.remove(FragmentMenuItems.DELETE);
157 protected long getSourceId() {
162 protected Config.IdType getSourceType() {
163 return Config.IdType.Playlist;
167 protected void removeFromPlaylist() {
168 mAdapter.remove(mSong);
169 mAdapter.buildCache();
170 mAdapter.notifyDataSetChanged();
171 MusicUtils.removeFromPlaylist(getActivity(), mSong.mSongId, mPlaylistId);
172 getLoaderManager().restartLoader(LOADER, null, PlaylistDetailFragment.this);
176 mPlaylistId = getArguments().getLong(Config.ID);
180 private void setupHero() {
181 mPlaylistImageView = (ImageView)mRootView.findViewById(R.id.image);
182 mHeaderContainer = mRootView.findViewById(R.id.playlist_header);
183 mNumberOfSongs = (TextView)mRootView.findViewById(R.id.number_of_songs_text);
184 mDurationOfPlaylist = (TextView)mRootView.findViewById(R.id.duration_text);
186 final ImageFetcher imageFetcher = ImageFetcher.getInstance(getActivity());
187 imageFetcher.loadPlaylistArtistImage(mPlaylistId, mPlaylistImageView);
190 private void setupSongList() {
191 mListView = (DragSortListView) mRootView.findViewById(R.id.list_base);
192 mListView.setOnScrollListener(PlaylistDetailFragment.this);
194 mAdapter = new ProfileSongAdapter(
197 R.layout.edit_track_list_item,
198 R.layout.faux_playlist_header
200 mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
202 public void onPopupMenuClicked(View v, int position) {
203 mPopupMenuHelper.showPopupMenu(v, position);
206 mListView.setAdapter(mAdapter);
207 // Release any references to the recycled Views
208 mListView.setRecyclerListener(new RecycleHolder());
209 // Play the selected song
210 mListView.setOnItemClickListener(this);
211 // Set the drop listener
212 mListView.setDropListener(this);
213 // Set the swipe to remove listener
214 mListView.setRemoveListener(this);
215 // Quick scroll while dragging
216 mListView.setDragScrollProfile(this);
218 // Adjust the progress bar padding to account for the header
219 int padTop = getResources().getDimensionPixelSize(R.dimen.playlist_detail_header_height);
220 mRootView.findViewById(R.id.progressbar).setPadding(0, padTop, 0, 0);
222 // set the loading and empty view container
223 mLoadingEmptyContainer =
224 (LoadingEmptyContainer)mRootView.findViewById(R.id.loading_empty_container);
225 setupNoResultsContainer(mLoadingEmptyContainer.getNoResultsContainer());
226 mListView.setEmptyView(mLoadingEmptyContainer);
229 private void setupNoResultsContainer(final NoResultsContainer container) {
230 container.setMainText(R.string.empty_playlist_main);
231 container.setSecondaryText(R.string.empty_playlist_secondary);
238 public float getSpeed(final float w, final long t) {
240 return mAdapter.getCount() / 0.001f;
250 public void remove(final int which) {
255 Song song = mAdapter.getItem(which);
256 mAdapter.remove(song);
257 mAdapter.buildCache();
258 mAdapter.notifyDataSetChanged();
259 final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", mPlaylistId);
260 getActivity().getContentResolver().delete(uri,
261 MediaStore.Audio.Playlists.Members.AUDIO_ID + "=" + song.mSongId,
264 MusicUtils.refresh();
271 public void drop(int from, int to) {
272 from = Math.max(ProfileSongAdapter.NUM_HEADERS, from);
273 to = Math.max(ProfileSongAdapter.NUM_HEADERS, to);
275 Song song = mAdapter.getItem(from);
276 mAdapter.remove(song);
277 mAdapter.insert(song, to);
278 mAdapter.buildCache();
279 mAdapter.notifyDataSetChanged();
281 final int realFrom = from - ProfileSongAdapter.NUM_HEADERS;
282 final int realTo = to - ProfileSongAdapter.NUM_HEADERS;
283 MediaStore.Audio.Playlists.Members.moveItem(getActivity().getContentResolver(),
284 mPlaylistId, realFrom, realTo);
291 public void onItemClick(final AdapterView<?> parent, final View view, final int position,
296 Cursor cursor = PlaylistSongLoader.makePlaylistSongCursor(getActivity(),
298 final long[] list = MusicUtils.getSongListForCursor(cursor);
299 MusicUtils.playAll(getActivity(), list, position - ProfileSongAdapter.NUM_HEADERS,
300 mPlaylistId, Config.IdType.Playlist, false);
306 public void onScrollStateChanged(AbsListView view, int scrollState) {
307 super.onScrollStateChanged(view, scrollState);
309 if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
310 mAdapter.setPauseDiskCache(true);
312 mAdapter.setPauseDiskCache(false);
313 mAdapter.notifyDataSetChanged();
317 protected int getHeaderHeight() { return mHeaderContainer.getHeight(); }
319 protected void setHeaderPosition(float y) {
320 // Offset the header height to account for the faux header
321 y = y - getResources().getDimension(R.dimen.header_bar_height);
322 mHeaderContainer.setY(y);
326 public Loader<List<Song>> onCreateLoader(int i, Bundle bundle) {
327 mLoadingEmptyContainer.showLoading();
329 return new PlaylistSongLoader(getActivity(), mPlaylistId);
333 public void onLoadFinished(final Loader<List<Song>> loader, final List<Song> data) {
334 if (data.isEmpty()) {
335 mLoadingEmptyContainer.showNoResults();
337 // hide the header container
338 mHeaderContainer.setVisibility(View.INVISIBLE);
343 // show the header container
344 mHeaderContainer.setVisibility(View.VISIBLE);
346 // pause notifying the adapter and make changes before re-enabling it so that the list
347 // view doesn't reset to the top of the list
348 mAdapter.setNotifyOnChange(false);
351 // Return the correct count
352 mAdapter.addAll(data);
354 mAdapter.buildCache();
355 // re-enable the notify by calling notify dataset changes
356 mAdapter.notifyDataSetChanged();
357 // set the number of songs
358 String numberOfSongs = MusicUtils.makeLabel(getActivity(), R.plurals.Nsongs,
360 mNumberOfSongs.setText(numberOfSongs);
364 // Add the data to the adapter
365 for (final Song song : data) {
366 duration += song.mDuration;
370 String durationString = MusicUtils.makeLongTimeString(getActivity(), duration);
371 mDurationOfPlaylist.setText(durationString);
376 public void onLoaderReset(final Loader<List<Song>> loader) {
377 // Clear the data in the adapter
382 public void restartLoader() {
383 lookupName(); // playlist name may have changed
384 if(mPlaylistName == null) {
385 // if name is null, we've been deleted, so close the this fragment
386 getContainingActivity().postRemoveFragment(this);
390 // since onCreateOptionsMenu can be called after onCreate it is possible for
391 // mActionMenuHelper to be null. In this case, don't bother updating the Name since when
392 // it does create it, it will use the updated name anyways
393 if (mActionMenuHelper != null) {
394 // update action bar title and popup menu handler
395 ((PlaylistPopupMenuHelper) mActionMenuHelper).updateName(mPlaylistName);
398 getContainingActivity().setActionBarTitle(mPlaylistName);
399 // and reload the song list
400 getLoaderManager().restartLoader(0, getArguments(), this);
404 public void onMetaChanged() {
405 super.onMetaChanged();
407 mAdapter.setCurrentlyPlayingTrack(MusicUtils.getCurrentTrack());
411 public void onPlaylistChanged() {
412 super.onPlaylistChanged();
418 public PagerAdapter.MusicFragments getMusicFragmentParent() {
419 return PagerAdapter.MusicFragments.PLAYLIST;