OSDN Git Service

Migrate to AndroidX
[android-x86/packages-apps-Eleven.git] / src / org / lineageos / eleven / ui / fragments / PlaylistDetailFragment.java
1 /*
2 * Copyright (C) 2014 The CyanogenMod Project
3 *
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
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16 package org.lineageos.eleven.ui.fragments;
17
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;
28
29 import androidx.loader.app.LoaderManager;
30 import androidx.loader.content.Loader;
31
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;
54
55 import java.util.List;
56 import java.util.TreeSet;
57
58 public class PlaylistDetailFragment extends FadingBarFragment implements
59         LoaderManager.LoaderCallbacks<List<Song>>, OnItemClickListener, DropListener,
60         RemoveListener, DragScrollProfile, IChildFragment {
61
62     /**
63      * LoaderCallbacks identifier
64      */
65     private static final int LOADER = 0;
66
67     private DragSortListView mListView;
68     private ProfileSongAdapter mAdapter;
69
70     private View mHeaderContainer;
71     private ImageView mPlaylistImageView;
72
73     private LoadingEmptyContainer mLoadingEmptyContainer;
74
75     private TextView mNumberOfSongs;
76     private TextView mDurationOfPlaylist;
77
78     /**
79      * The Id of the playlist the songs belong to
80      */
81     private long mPlaylistId;
82     private String mPlaylistName;
83
84     /**
85      * Pop up menu helper
86      */
87     private PopupMenuHelper mPopupMenuHelper;
88
89     @Override
90     protected String getTitle() { return mPlaylistName; }
91
92     @Override
93     protected int getLayoutToInflate() {
94         return R.layout.playlist_detail;
95     }
96
97     @Override
98     protected void onViewCreated() {
99         super.onViewCreated();
100         setupHero();
101         setupSongList();
102     }
103
104     private void lookupName() {
105         mPlaylistName = MusicUtils.getNameForPlaylist(getActivity(), mPlaylistId);
106     }
107
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);
114             }
115         };
116     }
117
118     @Override // DetailFragment
119     protected int getShuffleTitleId() { return R.string.menu_shuffle_playlist; }
120
121     @Override // DetailFragment
122     protected void playShuffled() {
123         MusicUtils.playPlaylist(getActivity(), mPlaylistId, true);
124     }
125
126     @Override
127     public void onActivityCreated(Bundle savedInstanceState) {
128         super.onActivityCreated(savedInstanceState);
129
130         LoaderManager lm = getLoaderManager();
131         lm.initLoader(0, getArguments(), this);
132     }
133
134     @Override
135     public void onCreate(Bundle savedInstanceState) {
136         super.onCreate(savedInstanceState);
137
138         mPopupMenuHelper = new SongPopupMenuHelper(getActivity(), getFragmentManager()) {
139             @Override
140             public Song getSong(int position) {
141                 if (position == 0) {
142                     return null;
143                 }
144
145                 return mAdapter.getItem(position);
146             }
147
148             @Override
149             protected void updateMenuIds(PopupMenuType type, TreeSet<Integer> set) {
150                 super.updateMenuIds(type, set);
151
152                 set.add(FragmentMenuItems.REMOVE_FROM_PLAYLIST);
153                 set.remove(FragmentMenuItems.DELETE);
154             }
155
156             @Override
157             protected long getSourceId() {
158                 return mPlaylistId;
159             }
160
161             @Override
162             protected Config.IdType getSourceType() {
163                 return Config.IdType.Playlist;
164             }
165
166             @Override
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);
173             }
174         };
175
176         mPlaylistId = getArguments().getLong(Config.ID);
177         lookupName();
178     }
179
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);
185
186         final ImageFetcher imageFetcher = ImageFetcher.getInstance(getActivity());
187         imageFetcher.loadPlaylistArtistImage(mPlaylistId, mPlaylistImageView);
188     }
189
190     private void setupSongList() {
191         mListView = (DragSortListView) mRootView.findViewById(R.id.list_base);
192         mListView.setOnScrollListener(PlaylistDetailFragment.this);
193
194         mAdapter = new ProfileSongAdapter(
195                 mPlaylistId,
196                 getActivity(),
197                 R.layout.edit_track_list_item,
198                 R.layout.faux_playlist_header
199         );
200         mAdapter.setPopupMenuClickedListener(new IPopupMenuCallback.IListener() {
201             @Override
202             public void onPopupMenuClicked(View v, int position) {
203                 mPopupMenuHelper.showPopupMenu(v, position);
204             }
205         });
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);
217
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);
221
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);
227     }
228
229     private void setupNoResultsContainer(final NoResultsContainer container) {
230         container.setMainText(R.string.empty_playlist_main);
231         container.setSecondaryText(R.string.empty_playlist_secondary);
232     }
233
234     /**
235      * {@inheritDoc}
236      */
237     @Override
238     public float getSpeed(final float w, final long t) {
239         if (w > 0.8f) {
240             return mAdapter.getCount() / 0.001f;
241         } else {
242             return 10.0f * w;
243         }
244     }
245
246     /**
247      * {@inheritDoc}
248      */
249     @Override
250     public void remove(final int which) {
251         if (which == 0) {
252             return;
253         }
254
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,
262                 null);
263
264         MusicUtils.refresh();
265     }
266
267     /**
268      * {@inheritDoc}
269      */
270     @Override
271     public void drop(int from, int to) {
272         from = Math.max(ProfileSongAdapter.NUM_HEADERS, from);
273         to = Math.max(ProfileSongAdapter.NUM_HEADERS, to);
274
275         Song song = mAdapter.getItem(from);
276         mAdapter.remove(song);
277         mAdapter.insert(song, to);
278         mAdapter.buildCache();
279         mAdapter.notifyDataSetChanged();
280
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);
285     }
286
287     /**
288      * {@inheritDoc}
289      */
290     @Override
291     public void onItemClick(final AdapterView<?> parent, final View view, final int position,
292                             final long id) {
293         if (position == 0) {
294             return;
295         }
296         Cursor cursor = PlaylistSongLoader.makePlaylistSongCursor(getActivity(),
297                 mPlaylistId);
298         final long[] list = MusicUtils.getSongListForCursor(cursor);
299         MusicUtils.playAll(getActivity(), list, position - ProfileSongAdapter.NUM_HEADERS,
300                 mPlaylistId, Config.IdType.Playlist, false);
301         cursor.close();
302         cursor = null;
303     }
304
305     @Override
306     public void onScrollStateChanged(AbsListView view, int scrollState) {
307         super.onScrollStateChanged(view, scrollState);
308
309         if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
310             mAdapter.setPauseDiskCache(true);
311         } else {
312             mAdapter.setPauseDiskCache(false);
313             mAdapter.notifyDataSetChanged();
314         }
315     }
316
317     protected int getHeaderHeight() { return mHeaderContainer.getHeight(); }
318
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);
323     }
324
325     @Override
326     public Loader<List<Song>> onCreateLoader(int i, Bundle bundle) {
327         mLoadingEmptyContainer.showLoading();
328
329         return new PlaylistSongLoader(getActivity(), mPlaylistId);
330     }
331
332     @Override
333     public void onLoadFinished(final Loader<List<Song>> loader, final List<Song> data) {
334         if (data.isEmpty()) {
335             mLoadingEmptyContainer.showNoResults();
336
337             // hide the header container
338             mHeaderContainer.setVisibility(View.INVISIBLE);
339
340             // Start fresh
341             mAdapter.unload();
342         } else {
343             // show the header container
344             mHeaderContainer.setVisibility(View.VISIBLE);
345
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);
349             // Start fresh
350             mAdapter.unload();
351             // Return the correct count
352             mAdapter.addAll(data);
353             // build the cache
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,
359                     data.size());
360             mNumberOfSongs.setText(numberOfSongs);
361
362             long duration = 0;
363
364             // Add the data to the adapter
365             for (final Song song : data) {
366                 duration += song.mDuration;
367             }
368
369             // set the duration
370             String durationString = MusicUtils.makeLongTimeString(getActivity(), duration);
371             mDurationOfPlaylist.setText(durationString);
372         }
373     }
374
375     @Override
376     public void onLoaderReset(final Loader<List<Song>> loader) {
377         // Clear the data in the adapter
378         mAdapter.unload();
379     }
380
381     @Override
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);
387             return;
388         }
389
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);
396         }
397
398         getContainingActivity().setActionBarTitle(mPlaylistName);
399         // and reload the song list
400         getLoaderManager().restartLoader(0, getArguments(), this);
401     }
402
403     @Override
404     public void onMetaChanged() {
405         super.onMetaChanged();
406
407         mAdapter.setCurrentlyPlayingTrack(MusicUtils.getCurrentTrack());
408     }
409
410     @Override
411     public void onPlaylistChanged() {
412         super.onPlaylistChanged();
413
414         restartLoader();
415     }
416
417     @Override
418     public PagerAdapter.MusicFragments getMusicFragmentParent() {
419         return PagerAdapter.MusicFragments.PLAYLIST;
420     }
421 }