From da7867c32da08c728db7f4bbbe460a292d0488b1 Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Sun, 27 Mar 2016 18:44:16 +0200 Subject: [PATCH] Folders and files got all menu items now. Added play next option for all bulk actions. --- .../gramophone/adapter/PlaylistAdapter.java | 10 +- .../gramophone/adapter/SongFileAdapter.java | 18 +- .../adapter/album/AlbumAdapter.java | 16 +- .../adapter/artist/ArtistAdapter.java | 18 +- .../adapter/song/ArtistSongAdapter.java | 15 +- .../gramophone/adapter/song/SongAdapter.java | 15 +- .../gramophone/helper/MusicPlayerRemote.java | 14 + .../helper/menu/SongMenuHelper.java | 7 +- .../helper/menu/SongsMenuHelper.java | 35 ++ .../gramophone/service/MusicService.java | 6 + .../mainactivity/folders/FoldersFragment.java | 405 ++++++++++-------- .../main/res/drawable/ic_redo_white_24dp.xml | 10 + ..._single_songs_playlist_songs_selection.xml | 16 +- app/src/main/res/menu/menu_item_directory.xml | 16 + app/src/main/res/menu/menu_item_file.xml | 40 ++ .../main/res/menu/menu_media_selection.xml | 18 +- .../res/menu/menu_playlists_selection.xml | 20 +- .../menu/menu_playlists_songs_selection.xml | 20 +- app/src/main/res/values/strings.xml | 1 + 19 files changed, 416 insertions(+), 284 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongsMenuHelper.java create mode 100644 app/src/main/res/drawable/ic_redo_white_24dp.xml diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/PlaylistAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/PlaylistAdapter.java index 32b0b6ed..ecb9e1b8 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/PlaylistAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/PlaylistAdapter.java @@ -16,11 +16,10 @@ import com.kabouzeid.appthemehelper.util.ATHUtil; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; -import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; import com.kabouzeid.gramophone.dialogs.ClearSmartPlaylistDialog; import com.kabouzeid.gramophone.dialogs.DeletePlaylistDialog; -import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.menu.PlaylistMenuHelper; +import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper; import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.loader.PlaylistSongLoader; import com.kabouzeid.gramophone.model.Playlist; @@ -148,11 +147,8 @@ public class PlaylistAdapter extends AbsMultiSelectAdapter selection) { if (callbacks == null) return; - switch (menuItem.getItemId()) { - case R.id.action_add_to_current_playing: - callbacks.onAddToCurrentPlaying(selection); - break; - case R.id.action_add_to_playlist: - callbacks.onAddToPlaylist(selection); - break; - case R.id.action_delete_from_device: - callbacks.onDeleteFromDevice(selection); - break; - } + callbacks.onMultipleItemAction(menuItem, selection); } @NonNull @@ -218,10 +208,6 @@ public class SongFileAdapter extends AbsMultiSelectAdapter files); - - void onAddToCurrentPlaying(ArrayList files); - - void onDeleteFromDevice(ArrayList files); + void onMultipleItemAction(MenuItem item, ArrayList files); } } \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/album/AlbumAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/album/AlbumAdapter.java index c36ea920..696dae5a 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/album/AlbumAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/album/AlbumAdapter.java @@ -17,11 +17,9 @@ import com.kabouzeid.appthemehelper.util.MaterialValueHelper; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; -import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; -import com.kabouzeid.gramophone.dialogs.DeleteSongsDialog; import com.kabouzeid.gramophone.glide.PhonographColoredTarget; import com.kabouzeid.gramophone.glide.SongGlideRequest; -import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper; import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.model.Album; import com.kabouzeid.gramophone.model.Song; @@ -173,17 +171,7 @@ public class AlbumAdapter extends AbsMultiSelectAdapter selection) { - switch (menuItem.getItemId()) { - case R.id.action_delete_from_device: - DeleteSongsDialog.create(getSongList(selection)).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); - break; - case R.id.action_add_to_playlist: - AddToPlaylistDialog.create(getSongList(selection)).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); - break; - case R.id.action_add_to_current_playing: - MusicPlayerRemote.enqueue(getSongList(selection)); - break; - } + SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId()); } @NonNull diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/artist/ArtistAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/artist/ArtistAdapter.java index ee0817a0..9194d5c3 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/artist/ArtistAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/artist/ArtistAdapter.java @@ -20,13 +20,11 @@ import com.kabouzeid.appthemehelper.util.MaterialValueHelper; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; -import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; -import com.kabouzeid.gramophone.dialogs.DeleteSongsDialog; import com.kabouzeid.gramophone.glide.PhonographColoredTarget; import com.kabouzeid.gramophone.glide.artistimage.ArtistImage; import com.kabouzeid.gramophone.glide.palette.BitmapPaletteTranscoder; import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; -import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper; import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.loader.ArtistSongLoader; import com.kabouzeid.gramophone.model.Artist; @@ -175,24 +173,14 @@ public class ArtistAdapter extends AbsMultiSelectAdapter selection) { - switch (menuItem.getItemId()) { - case R.id.action_delete_from_device: - DeleteSongsDialog.create(getSongList(selection)).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); - break; - case R.id.action_add_to_playlist: - AddToPlaylistDialog.create(getSongList(selection)).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); - break; - case R.id.action_add_to_current_playing: - MusicPlayerRemote.enqueue(getSongList(selection)); - break; - } + SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId()); } @NonNull private ArrayList getSongList(@NonNull List artists) { final ArrayList songs = new ArrayList<>(); for (Artist artist : artists) { - songs.addAll(ArtistSongLoader.getArtistSongList(activity, artist.id)); + songs.addAll(ArtistSongLoader.getArtistSongList(activity, artist.id)); // maybe async in future? } return songs; } diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/ArtistSongAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/ArtistSongAdapter.java index c66e8981..c14b7202 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/ArtistSongAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/ArtistSongAdapter.java @@ -17,11 +17,10 @@ import android.widget.TextView; import com.afollestad.materialcab.MaterialCab; import com.bumptech.glide.Glide; import com.kabouzeid.gramophone.R; -import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; -import com.kabouzeid.gramophone.dialogs.DeleteSongsDialog; import com.kabouzeid.gramophone.glide.SongGlideRequest; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.menu.SongMenuHelper; +import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper; import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.util.NavigationUtil; @@ -137,17 +136,7 @@ public class ArtistSongAdapter extends ArrayAdapter implements MaterialCab } private void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList selection) { - switch (menuItem.getItemId()) { - case R.id.action_delete_from_device: - DeleteSongsDialog.create(selection).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); - break; - case R.id.action_add_to_playlist: - AddToPlaylistDialog.create(selection).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); - break; - case R.id.action_add_to_current_playing: - MusicPlayerRemote.enqueue(selection); - break; - } + SongsMenuHelper.handleMenuClick(activity, selection, menuItem.getItemId()); } protected void toggleChecked(Song song) { diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java index 43ae69f1..c65b3c70 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java @@ -18,12 +18,11 @@ import com.kabouzeid.appthemehelper.util.MaterialValueHelper; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; -import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; -import com.kabouzeid.gramophone.dialogs.DeleteSongsDialog; import com.kabouzeid.gramophone.glide.PhonographColoredTarget; import com.kabouzeid.gramophone.glide.SongGlideRequest; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.menu.SongMenuHelper; +import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper; import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.util.MusicUtil; @@ -173,17 +172,7 @@ public class SongAdapter extends AbsMultiSelectAdapter selection) { - switch (menuItem.getItemId()) { - case R.id.action_delete_from_device: - DeleteSongsDialog.create(selection).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); - break; - case R.id.action_add_to_playlist: - AddToPlaylistDialog.create(selection).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); - break; - case R.id.action_add_to_current_playing: - MusicPlayerRemote.enqueue(selection); - break; - } + SongsMenuHelper.handleMenuClick(activity, selection, menuItem.getItemId()); } @NonNull diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/MusicPlayerRemote.java b/app/src/main/java/com/kabouzeid/gramophone/helper/MusicPlayerRemote.java index 4fd04cea..1306cd59 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/MusicPlayerRemote.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/MusicPlayerRemote.java @@ -292,6 +292,20 @@ public class MusicPlayerRemote { return false; } + public static boolean playNext(@NonNull ArrayList songs) { + if (musicService != null) { + if (getPlayingQueue().size() > 0) { + musicService.addSongs(getPosition() + 1, songs); + } else { + openQueue(songs, 0, false); + } + final String toast = songs.size() == 1 ? musicService.getResources().getString(R.string.added_title_to_playing_queue) : musicService.getResources().getString(R.string.added_x_titles_to_playing_queue, songs.size()); + Toast.makeText(musicService, toast, Toast.LENGTH_SHORT).show(); + return true; + } + return false; + } + public static boolean enqueue(Song song) { if (musicService != null) { if (getPlayingQueue().size() > 0) { diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongMenuHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongMenuHelper.java index b7bdc9e0..a1767662 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongMenuHelper.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongMenuHelper.java @@ -2,6 +2,7 @@ package com.kabouzeid.gramophone.helper.menu; import android.content.Intent; import android.support.annotation.NonNull; +import android.support.v4.app.FragmentActivity; import android.support.v7.app.AppCompatActivity; import android.view.MenuItem; import android.view.View; @@ -25,8 +26,8 @@ import com.kabouzeid.gramophone.util.NavigationUtil; public class SongMenuHelper { public static final int MENU_RES = R.menu.menu_item_song; - public static boolean handleMenuClick(@NonNull AppCompatActivity activity, @NonNull Song song, @NonNull MenuItem item) { - switch (item.getItemId()) { + public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull Song song, int menuItemId) { + switch (menuItemId) { case R.id.action_set_as_ringtone: MusicUtil.setRingtone(activity, song.id); return true; @@ -86,7 +87,7 @@ public class SongMenuHelper { @Override public boolean onMenuItemClick(MenuItem item) { - return handleMenuClick(activity, getSong(), item); + return handleMenuClick(activity, getSong(), item.getItemId()); } public abstract Song getSong(); diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongsMenuHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongsMenuHelper.java new file mode 100644 index 00000000..5adb0868 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/menu/SongsMenuHelper.java @@ -0,0 +1,35 @@ +package com.kabouzeid.gramophone.helper.menu; + +import android.support.annotation.NonNull; +import android.support.v4.app.FragmentActivity; + +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; +import com.kabouzeid.gramophone.dialogs.DeleteSongsDialog; +import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.model.Song; + +import java.util.ArrayList; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public class SongsMenuHelper { + public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull ArrayList songs, int menuItemId) { + switch (menuItemId) { + case R.id.action_play_next: + MusicPlayerRemote.playNext(songs); + return true; + case R.id.action_add_to_current_playing: + MusicPlayerRemote.enqueue(songs); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialog.create(songs).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); + return true; + case R.id.action_delete_from_device: + DeleteSongsDialog.create(songs).show(activity.getSupportFragmentManager(), "DELETE_SONGS"); + return true; + } + return false; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java index dbfe0913..54853205 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -653,6 +653,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP notifyChange(QUEUE_CHANGED); } + public void addSongs(int position, List songs) { + playingQueue.addAll(position, songs); + originalPlayingQueue.addAll(position, songs); + notifyChange(QUEUE_CHANGED); + } + public void addSongs(List songs) { playingQueue.addAll(songs); originalPlayingQueue.addAll(songs); diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/folders/FoldersFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/folders/FoldersFragment.java index 315ab7f6..eab0990d 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/folders/FoldersFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivity/folders/FoldersFragment.java @@ -42,9 +42,9 @@ import com.kabouzeid.appthemehelper.common.ATHToolbarActivity; import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.SongFileAdapter; -import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; -import com.kabouzeid.gramophone.dialogs.DeleteSongsDialog; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.helper.menu.SongMenuHelper; +import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper; import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.misc.WrappedAsyncTaskLoader; import com.kabouzeid.gramophone.model.Song; @@ -75,11 +75,6 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi private static final int LOADER_ID = 1; - private static final int ADD_TO_PLAYLIST = 0; - private static final int ADD_TO_CURRENT_PLAYING = 1; - private static final int DELETE = 2; - private static final int PLAY = 3; - protected static final String PATH = "path"; protected static final String CRUMBS = "crumbs"; @@ -301,20 +296,58 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi if (file.isDirectory()) { setCrumb(new BreadCrumbLayout.Crumb(file), true); } else { - List files = new LinkedList<>(); - files.add(file.getParentFile()); - FileFilter fileFilter = new FileFilter() { @Override public boolean accept(File pathname) { return !pathname.isDirectory() && getFileFilter().accept(pathname); } }; - - new ListSongsAsyncTask(this, PLAY, file).execute(new ListSongsAsyncTask.LoadingInfo(files, fileFilter, getFileComparator())); + new ListSongsAsyncTask(getActivity(), file, new ListSongsAsyncTask.OnSongsListedCallback() { + @Override + public void onSongsListed(@NonNull ArrayList songs, Object extra) { + File file = (File) extra; + int startIndex = -1; + for (int i = 0; i < songs.size(); i++) { + if (file.getPath().equals(songs.get(i).data)) { // path is already canonical here + startIndex = i; + break; + } + } + if (startIndex > -1) { + MusicPlayerRemote.openQueue(songs, startIndex, true); + } else { + final File finalFile = file; + Snackbar.make(coordinatorLayout, Html.fromHtml(String.format(getString(R.string.not_listed_in_media_store), file.getName())), Snackbar.LENGTH_LONG) + .setAction(R.string.action_scan, new View.OnClickListener() { + @Override + public void onClick(View v) { + new ListPathsAsyncTask(getActivity(), new ListPathsAsyncTask.OnPathsListedCallback() { + @Override + public void onPathsListed(@Nullable String[] paths) { + scanPaths(paths); + } + }).execute(new ListPathsAsyncTask.LoadingInfo(finalFile, getFileFilter())); + } + }) + .setActionTextColor(ThemeStore.accentColor(getActivity())) + .show(); + } + } + }).execute(new ListSongsAsyncTask.LoadingInfo(toList(file.getParentFile()), fileFilter, getFileComparator())); } } + @Override + public void onMultipleItemAction(MenuItem item, ArrayList files) { + final int itemId = item.getItemId(); + new ListSongsAsyncTask(getActivity(), null, new ListSongsAsyncTask.OnSongsListedCallback() { + @Override + public void onSongsListed(@NonNull ArrayList songs, Object extra) { + SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId); + } + }).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator())); + } + @Override public void onFileMenuClicked(final File file, View view) { PopupMenu popupMenu = new PopupMenu(getActivity(), view); @@ -323,12 +356,30 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_delete_from_device: + new ListSongsAsyncTask(getActivity(), null, new ListSongsAsyncTask.OnSongsListedCallback() { + @Override + public void onSongsListed(@NonNull ArrayList songs, Object extra) { + SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId); + } + }).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), getFileFilter(), getFileComparator())); + return true; case R.id.action_set_as_start_directory: PreferenceUtil.getInstance(getActivity()).setStartDirectory(file); + Toast.makeText(getActivity(), String.format(getString(R.string.new_start_directory), file.getPath()), Toast.LENGTH_SHORT).show(); return true; case R.id.action_scan: - scan(file); + new ListPathsAsyncTask(getActivity(), new ListPathsAsyncTask.OnPathsListedCallback() { + @Override + public void onPathsListed(@Nullable String[] paths) { + scanPaths(paths); + } + }).execute(new ListPathsAsyncTask.LoadingInfo(file, getFileFilter())); return true; } return false; @@ -339,9 +390,32 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { + final int itemId = item.getItemId(); + switch (itemId) { + case R.id.action_play_next: + case R.id.action_add_to_current_playing: + case R.id.action_add_to_playlist: + case R.id.action_go_to_album: + case R.id.action_go_to_artist: + case R.id.action_share: + case R.id.action_tag_editor: + case R.id.action_details: + case R.id.action_set_as_ringtone: + case R.id.action_delete_from_device: + new ListSongsAsyncTask(getActivity(), null, new ListSongsAsyncTask.OnSongsListedCallback() { + @Override + public void onSongsListed(@NonNull ArrayList songs, Object extra) { + SongMenuHelper.handleMenuClick(getActivity(), songs.get(0), itemId); + } + }).execute(new ListSongsAsyncTask.LoadingInfo(toList(file), getFileFilter(), getFileComparator())); + return true; case R.id.action_scan: - scan(file); + new ListPathsAsyncTask(getActivity(), new ListPathsAsyncTask.OnPathsListedCallback() { + @Override + public void onPathsListed(@Nullable String[] paths) { + scanPaths(paths); + } + }).execute(new ListPathsAsyncTask.LoadingInfo(file, getFileFilter())); return true; } return false; @@ -351,23 +425,10 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi popupMenu.show(); } - private void scan(File file) { - new ScanFilesAsyncTask(this, file).execute(getFileFilter()); - } - - @Override - public void onAddToPlaylist(ArrayList files) { - new ListSongsAsyncTask(this, ADD_TO_PLAYLIST, null).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator())); - } - - @Override - public void onAddToCurrentPlaying(ArrayList files) { - new ListSongsAsyncTask(this, ADD_TO_CURRENT_PLAYING, null).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator())); - } - - @Override - public void onDeleteFromDevice(ArrayList files) { - new ListSongsAsyncTask(this, DELETE, null).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator())); + private ArrayList toList(File file) { + ArrayList files = new ArrayList<>(1); + files.add(file); + return files; } Comparator fileComparator = new Comparator() { @@ -419,6 +480,60 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi } } + private void scanPaths(@Nullable String[] toBeScanned) { + if (getActivity() == null) return; + if (toBeScanned == null || toBeScanned.length < 1) { + Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); + } else { + MediaScannerConnection.scanFile(getActivity().getApplicationContext(), toBeScanned, null, new UpdateToastCompletionListener(getActivity(), toBeScanned)); + } + } + + private static class UpdateToastCompletionListener implements MediaScannerConnection.OnScanCompletedListener { + int scanned = 0; + int failed = 0; + + private final String[] toBeScanned; + + private final String scannedFiles; + private final String couldNotScanFiles; + + private final WeakReference toastWeakReference; + private final WeakReference activityWeakReference; + + @SuppressLint("ShowToast") + public UpdateToastCompletionListener(Activity activity, String[] toBeScanned) { + this.toBeScanned = toBeScanned; + scannedFiles = activity.getString(R.string.scanned_files); + couldNotScanFiles = activity.getString(R.string.could_not_scan_files); + toastWeakReference = new WeakReference<>(Toast.makeText(activity, "", Toast.LENGTH_SHORT)); + activityWeakReference = new WeakReference<>(activity); + } + + @Override + public void onScanCompleted(final String path, final Uri uri) { + Activity activity = activityWeakReference.get(); + if (activity != null) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + Toast toast = toastWeakReference.get(); + if (toast != null) { + if (uri == null) { + failed++; + } else { + scanned++; + } + String text = " " + String.format(scannedFiles, scanned, toBeScanned.length) + (failed > 0 ? " " + String.format(couldNotScanFiles, failed) : ""); + toast.setText(text); + toast.show(); + } + } + }); + } + } + } + private void updateAdapter(@NonNull List files) { adapter.swapDataSet(files); BreadCrumbLayout.Crumb crumb = getActiveCrumb(); @@ -470,60 +585,23 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi } } - private void onSongsListed(int requestCode, ArrayList songs, Object extra) { - switch (requestCode) { - case ADD_TO_PLAYLIST: - AddToPlaylistDialog.create(songs).show(getFragmentManager(), "ADD_PLAYLIST"); - break; - case ADD_TO_CURRENT_PLAYING: - MusicPlayerRemote.enqueue(songs); - break; - case DELETE: - DeleteSongsDialog.create(songs).show(getFragmentManager(), "DELETE_SONGS"); - break; - case PLAY: - File file = (File) extra; - int startIndex = -1; - for (int i = 0; i < songs.size(); i++) { - if (file.getPath().equals(songs.get(i).data)) { // path is already canonical here - startIndex = i; - break; - } - } - if (startIndex > -1) { - MusicPlayerRemote.openQueue(songs, startIndex, true); - } else { - final File finalFile = file; - Snackbar.make(coordinatorLayout, Html.fromHtml(String.format(getString(R.string.not_listed_in_media_store), file.getName())), Snackbar.LENGTH_LONG) - .setAction(R.string.action_scan, new View.OnClickListener() { - @Override - public void onClick(View v) { - scan(finalFile); - } - }) - .setActionTextColor(ThemeStore.accentColor(getActivity())) - .show(); - } - break; - } - } - private static class ListSongsAsyncTask extends DialogAsyncTask> { - private WeakReference fragmentWeakReference; - private final int requestCode; + private WeakReference contextWeakReference; + private WeakReference callbackWeakReference; private final Object extra; - public ListSongsAsyncTask(FoldersFragment foldersFragment, int requestCode, Object extra) { - super(foldersFragment.getActivity(), R.string.listing_files); - this.requestCode = requestCode; + public ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) { + super(context, R.string.listing_files); this.extra = extra; - fragmentWeakReference = new WeakReference<>(foldersFragment); + contextWeakReference = new WeakReference<>(context); + callbackWeakReference = new WeakReference<>(callback); } @Override protected void onPreExecute() { super.onPreExecute(); - checkFragmentReference(); + checkCallbackReference(); + checkContextReference(); } @Override @@ -532,14 +610,16 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi LoadingInfo info = params[0]; List files = FileUtil.listFilesDeep(info.files, info.fileFilter); - if (isCancelled() || checkFragmentReference() == null) return null; + if (isCancelled() || checkContextReference() == null || checkCallbackReference() == null) + return null; Collections.sort(files, info.fileComparator); - FoldersFragment fragment = checkFragmentReference(); - if (isCancelled() || fragment == null) return null; + Context context = checkContextReference(); + if (isCancelled() || context == null || checkCallbackReference() == null) + return null; - return FileUtil.matchFilesWithMediaStore(fragment.getActivity(), files); + return FileUtil.matchFilesWithMediaStore(context, files); } catch (Exception e) { e.printStackTrace(); cancel(false); @@ -550,9 +630,25 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi @Override protected void onPostExecute(ArrayList songs) { super.onPostExecute(songs); - FoldersFragment fragment = checkFragmentReference(); - if (!songs.isEmpty() && fragment != null) - fragment.onSongsListed(requestCode, songs, extra); + OnSongsListedCallback callback = checkCallbackReference(); + if (songs != null && callback != null && !songs.isEmpty()) + callback.onSongsListed(songs, extra); + } + + private Context checkContextReference() { + Context context = contextWeakReference.get(); + if (context == null) { + cancel(false); + } + return context; + } + + private OnSongsListedCallback checkCallbackReference() { + OnSongsListedCallback callback = callbackWeakReference.get(); + if (callback == null) { + cancel(false); + } + return callback; } public static class LoadingInfo { @@ -567,118 +663,57 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi } } - /** - * @return true if the task was canceled - */ - private FoldersFragment checkFragmentReference() { - FoldersFragment fragment = fragmentWeakReference.get(); - if (fragment == null) { - cancel(false); - } - return fragment; + public interface OnSongsListedCallback { + void onSongsListed(@NonNull ArrayList songs, Object extra); } } - private void onScanPaths(final String[] toBeScanned) { - if (getActivity() == null) return; - if (toBeScanned == null || toBeScanned.length < 1) { - Toast.makeText(getActivity(), R.string.nothing_to_scan, Toast.LENGTH_SHORT).show(); - } else { - MediaScannerConnection.scanFile(getActivity().getApplicationContext(), toBeScanned, null, new UpdateToastCompletionListener(getActivity(), toBeScanned)); - } - } + private static class ListPathsAsyncTask extends DialogAsyncTask { + private WeakReference onPathsListedCallbackWeakReference; - private static class UpdateToastCompletionListener implements MediaScannerConnection.OnScanCompletedListener { - int scanned = 0; - int failed = 0; - - private final String[] toBeScanned; - - private final String scannedFiles; - private final String couldNotScanFiles; - - private final WeakReference toastWeakReference; - private final WeakReference activityWeakReference; - - @SuppressLint("ShowToast") - public UpdateToastCompletionListener(Activity activity, String[] toBeScanned) { - this.toBeScanned = toBeScanned; - scannedFiles = activity.getString(R.string.scanned_files); - couldNotScanFiles = activity.getString(R.string.could_not_scan_files); - toastWeakReference = new WeakReference<>(Toast.makeText(activity, "", Toast.LENGTH_SHORT)); - activityWeakReference = new WeakReference<>(activity); - } - - @Override - public void onScanCompleted(final String path, final Uri uri) { - Activity activity = activityWeakReference.get(); - if (activity != null) { - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - Toast toast = toastWeakReference.get(); - if (toast != null) { - if (uri == null) { - failed++; - } else { - scanned++; - } - String text = " " + String.format(scannedFiles, scanned, toBeScanned.length) + (failed > 0 ? " " + String.format(couldNotScanFiles, failed) : ""); - toast.setText(text); - toast.show(); - } - } - }); - } - } - } - - private static class ScanFilesAsyncTask extends DialogAsyncTask { - private final File file; - private WeakReference fragmentWeakReference; - - public ScanFilesAsyncTask(FoldersFragment foldersFragment, File file) { - super(foldersFragment.getActivity(), R.string.listing_files); - this.file = file; - fragmentWeakReference = new WeakReference<>(foldersFragment); + public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) { + super(context, R.string.listing_files); + onPathsListedCallbackWeakReference = new WeakReference<>(callback); } @Override protected void onPreExecute() { super.onPreExecute(); - checkFragmentReference(); + checkCallbackReference(); } @Override - protected String[] doInBackground(FileFilter... params) { + protected String[] doInBackground(LoadingInfo... params) { try { - if (isCancelled() || checkFragmentReference() == null) return null; + if (isCancelled() || checkCallbackReference() == null) return null; - final String[] toBeScanned; + LoadingInfo info = params[0]; - if (file.isDirectory()) { - List files = FileUtil.listFilesDeep(file, params[0]); + final String[] paths; - if (isCancelled() || checkFragmentReference() == null) return null; + if (info.file.isDirectory()) { + List files = FileUtil.listFilesDeep(info.file, info.fileFilter); - toBeScanned = new String[files.size()]; + if (isCancelled() || checkCallbackReference() == null) return null; + + paths = new String[files.size()]; for (int i = 0; i < files.size(); i++) { File f = files.get(i); try { - toBeScanned[i] = f.getCanonicalPath(); // canonical path is important here because we want to compare the path with the media store entry later + paths[i] = f.getCanonicalPath(); // canonical path is important here because we want to compare the path with the media store entry later } catch (IOException e) { e.printStackTrace(); - toBeScanned[i] = f.getPath(); + paths[i] = f.getPath(); } - if (isCancelled() || checkFragmentReference() == null) return toBeScanned; + if (isCancelled() || checkCallbackReference() == null) return paths; } } else { - toBeScanned = new String[1]; - toBeScanned[0] = file.getPath(); + paths = new String[1]; + paths[0] = info.file.getPath(); } - return toBeScanned; + return paths; } catch (Exception e) { e.printStackTrace(); cancel(false); @@ -687,20 +722,34 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi } @Override - protected void onPostExecute(String[] toBeScanned) { - super.onPostExecute(toBeScanned); - FoldersFragment fragment = checkFragmentReference(); - if (fragment != null) { - fragment.onScanPaths(toBeScanned); + protected void onPostExecute(String[] paths) { + super.onPostExecute(paths); + OnPathsListedCallback callback = checkCallbackReference(); + if (callback != null) { + callback.onPathsListed(paths); } } - private FoldersFragment checkFragmentReference() { - FoldersFragment fragment = fragmentWeakReference.get(); - if (fragment == null) { + private OnPathsListedCallback checkCallbackReference() { + OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get(); + if (callback == null) { cancel(false); } - return fragment; + return callback; + } + + public static class LoadingInfo { + public final File file; + public final FileFilter fileFilter; + + public LoadingInfo(File file, FileFilter fileFilter) { + this.file = file; + this.fileFilter = fileFilter; + } + } + + public interface OnPathsListedCallback { + void onPathsListed(@Nullable String[] paths); } } diff --git a/app/src/main/res/drawable/ic_redo_white_24dp.xml b/app/src/main/res/drawable/ic_redo_white_24dp.xml new file mode 100644 index 00000000..ece45645 --- /dev/null +++ b/app/src/main/res/drawable/ic_redo_white_24dp.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/menu/menu_cannot_delete_single_songs_playlist_songs_selection.xml b/app/src/main/res/menu/menu_cannot_delete_single_songs_playlist_songs_selection.xml index 9b1961e2..a516b873 100644 --- a/app/src/main/res/menu/menu_cannot_delete_single_songs_playlist_songs_selection.xml +++ b/app/src/main/res/menu/menu_cannot_delete_single_songs_playlist_songs_selection.xml @@ -3,15 +3,21 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + android:id="@+id/action_play_next" + android:icon="@drawable/ic_redo_white_24dp" + android:title="@string/action_play_next" + app:showAsAction="always" /> + app:showAsAction="always" /> + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_item_directory.xml b/app/src/main/res/menu/menu_item_directory.xml index b127bf3f..01f67ba7 100644 --- a/app/src/main/res/menu/menu_item_directory.xml +++ b/app/src/main/res/menu/menu_item_directory.xml @@ -1,6 +1,18 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_item_file.xml b/app/src/main/res/menu/menu_item_file.xml index 7a2c84ce..28787c59 100644 --- a/app/src/main/res/menu/menu_item_file.xml +++ b/app/src/main/res/menu/menu_item_file.xml @@ -1,8 +1,48 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_media_selection.xml b/app/src/main/res/menu/menu_media_selection.xml index 26a0068f..20ab9883 100644 --- a/app/src/main/res/menu/menu_media_selection.xml +++ b/app/src/main/res/menu/menu_media_selection.xml @@ -3,21 +3,27 @@ xmlns:app="http://schemas.android.com/apk/res-auto"> + android:id="@+id/action_play_next" + android:icon="@drawable/ic_redo_white_24dp" + android:title="@string/action_play_next" + app:showAsAction="always" /> + app:showAsAction="always" /> + + + app:showAsAction="always" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_playlists_selection.xml b/app/src/main/res/menu/menu_playlists_selection.xml index f486f55e..aea5a2db 100644 --- a/app/src/main/res/menu/menu_playlists_selection.xml +++ b/app/src/main/res/menu/menu_playlists_selection.xml @@ -1,23 +1,29 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:id="@+id/action_play_next" + android:icon="@drawable/ic_redo_white_24dp" + android:title="@string/action_play_next" + app:showAsAction="always" /> + app:showAsAction="always" /> + + + app:showAsAction="always" /> \ No newline at end of file diff --git a/app/src/main/res/menu/menu_playlists_songs_selection.xml b/app/src/main/res/menu/menu_playlists_songs_selection.xml index 884d43f9..ff356b62 100644 --- a/app/src/main/res/menu/menu_playlists_songs_selection.xml +++ b/app/src/main/res/menu/menu_playlists_songs_selection.xml @@ -1,23 +1,29 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + android:id="@+id/action_play_next" + android:icon="@drawable/ic_redo_white_24dp" + android:title="@string/action_play_next" + app:showAsAction="always" /> + app:showAsAction="always" /> + + + app:showAsAction="always" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ec7d7f03..7d13e0f3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -245,4 +245,5 @@ Scanned %1$d of %2$d files. Could not scan %d files. Listing files + %s is the new start directory.