Folders and files got all menu items now. Added play next option for all bulk actions.

This commit is contained in:
Karim Abou Zeid 2016-03-27 18:44:16 +02:00
commit da7867c32d
19 changed files with 416 additions and 284 deletions

View file

@ -16,11 +16,10 @@ import com.kabouzeid.appthemehelper.util.ATHUtil;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter;
import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder;
import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog;
import com.kabouzeid.gramophone.dialogs.ClearSmartPlaylistDialog; import com.kabouzeid.gramophone.dialogs.ClearSmartPlaylistDialog;
import com.kabouzeid.gramophone.dialogs.DeletePlaylistDialog; 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.PlaylistMenuHelper;
import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper;
import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.interfaces.CabHolder;
import com.kabouzeid.gramophone.loader.PlaylistSongLoader; import com.kabouzeid.gramophone.loader.PlaylistSongLoader;
import com.kabouzeid.gramophone.model.Playlist; import com.kabouzeid.gramophone.model.Playlist;
@ -148,11 +147,8 @@ public class PlaylistAdapter extends AbsMultiSelectAdapter<PlaylistAdapter.ViewH
DeletePlaylistDialog.create(selection).show(activity.getSupportFragmentManager(), "DELETE_PLAYLIST"); DeletePlaylistDialog.create(selection).show(activity.getSupportFragmentManager(), "DELETE_PLAYLIST");
} }
break; break;
case R.id.action_add_to_playlist: default:
AddToPlaylistDialog.create(getSongList(selection)).show(activity.getSupportFragmentManager(), "ADD_PLAYLIST"); SongsMenuHelper.handleMenuClick(activity, getSongList(selection), menuItem.getItemId());
break;
case R.id.action_add_to_current_playing:
MusicPlayerRemote.enqueue(getSongList(selection));
break; break;
} }
} }

View file

@ -152,17 +152,7 @@ public class SongFileAdapter extends AbsMultiSelectAdapter<SongFileAdapter.ViewH
@Override @Override
protected void onMultipleItemAction(MenuItem menuItem, ArrayList<File> selection) { protected void onMultipleItemAction(MenuItem menuItem, ArrayList<File> selection) {
if (callbacks == null) return; if (callbacks == null) return;
switch (menuItem.getItemId()) { callbacks.onMultipleItemAction(menuItem, selection);
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;
}
} }
@NonNull @NonNull
@ -218,10 +208,6 @@ public class SongFileAdapter extends AbsMultiSelectAdapter<SongFileAdapter.ViewH
void onFileMenuClicked(File file, View view); void onFileMenuClicked(File file, View view);
void onAddToPlaylist(ArrayList<File> files); void onMultipleItemAction(MenuItem item, ArrayList<File> files);
void onAddToCurrentPlaying(ArrayList<File> files);
void onDeleteFromDevice(ArrayList<File> files);
} }
} }

View file

@ -17,11 +17,9 @@ import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter;
import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; 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.PhonographColoredTarget;
import com.kabouzeid.gramophone.glide.SongGlideRequest; 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.interfaces.CabHolder;
import com.kabouzeid.gramophone.model.Album; import com.kabouzeid.gramophone.model.Album;
import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.model.Song;
@ -173,17 +171,7 @@ public class AlbumAdapter extends AbsMultiSelectAdapter<AlbumAdapter.ViewHolder,
@Override @Override
protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList<Album> selection) { protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList<Album> selection) {
switch (menuItem.getItemId()) { SongsMenuHelper.handleMenuClick(activity, getSongList(selection), 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;
}
} }
@NonNull @NonNull

View file

@ -20,13 +20,11 @@ import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter;
import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; 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.PhonographColoredTarget;
import com.kabouzeid.gramophone.glide.artistimage.ArtistImage; import com.kabouzeid.gramophone.glide.artistimage.ArtistImage;
import com.kabouzeid.gramophone.glide.palette.BitmapPaletteTranscoder; import com.kabouzeid.gramophone.glide.palette.BitmapPaletteTranscoder;
import com.kabouzeid.gramophone.glide.palette.BitmapPaletteWrapper; 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.interfaces.CabHolder;
import com.kabouzeid.gramophone.loader.ArtistSongLoader; import com.kabouzeid.gramophone.loader.ArtistSongLoader;
import com.kabouzeid.gramophone.model.Artist; import com.kabouzeid.gramophone.model.Artist;
@ -175,24 +173,14 @@ public class ArtistAdapter extends AbsMultiSelectAdapter<ArtistAdapter.ViewHolde
@Override @Override
protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList<Artist> selection) { protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList<Artist> selection) {
switch (menuItem.getItemId()) { SongsMenuHelper.handleMenuClick(activity, getSongList(selection), 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;
}
} }
@NonNull @NonNull
private ArrayList<Song> getSongList(@NonNull List<Artist> artists) { private ArrayList<Song> getSongList(@NonNull List<Artist> artists) {
final ArrayList<Song> songs = new ArrayList<>(); final ArrayList<Song> songs = new ArrayList<>();
for (Artist artist : artists) { for (Artist artist : artists) {
songs.addAll(ArtistSongLoader.getArtistSongList(activity, artist.id)); songs.addAll(ArtistSongLoader.getArtistSongList(activity, artist.id)); // maybe async in future?
} }
return songs; return songs;
} }

View file

@ -17,11 +17,10 @@ import android.widget.TextView;
import com.afollestad.materialcab.MaterialCab; import com.afollestad.materialcab.MaterialCab;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.kabouzeid.gramophone.R; 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.glide.SongGlideRequest;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.menu.SongMenuHelper; import com.kabouzeid.gramophone.helper.menu.SongMenuHelper;
import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper;
import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.interfaces.CabHolder;
import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.util.NavigationUtil; import com.kabouzeid.gramophone.util.NavigationUtil;
@ -137,17 +136,7 @@ public class ArtistSongAdapter extends ArrayAdapter<Song> implements MaterialCab
} }
private void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList<Song> selection) { private void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList<Song> selection) {
switch (menuItem.getItemId()) { SongsMenuHelper.handleMenuClick(activity, selection, 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;
}
} }
protected void toggleChecked(Song song) { protected void toggleChecked(Song song) {

View file

@ -18,12 +18,11 @@ import com.kabouzeid.appthemehelper.util.MaterialValueHelper;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter; import com.kabouzeid.gramophone.adapter.base.AbsMultiSelectAdapter;
import com.kabouzeid.gramophone.adapter.base.MediaEntryViewHolder; 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.PhonographColoredTarget;
import com.kabouzeid.gramophone.glide.SongGlideRequest; import com.kabouzeid.gramophone.glide.SongGlideRequest;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.menu.SongMenuHelper; import com.kabouzeid.gramophone.helper.menu.SongMenuHelper;
import com.kabouzeid.gramophone.helper.menu.SongsMenuHelper;
import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.interfaces.CabHolder;
import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.util.MusicUtil; import com.kabouzeid.gramophone.util.MusicUtil;
@ -173,17 +172,7 @@ public class SongAdapter extends AbsMultiSelectAdapter<SongAdapter.ViewHolder, S
@Override @Override
protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList<Song> selection) { protected void onMultipleItemAction(@NonNull MenuItem menuItem, @NonNull ArrayList<Song> selection) {
switch (menuItem.getItemId()) { SongsMenuHelper.handleMenuClick(activity, selection, 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;
}
} }
@NonNull @NonNull

View file

@ -292,6 +292,20 @@ public class MusicPlayerRemote {
return false; return false;
} }
public static boolean playNext(@NonNull ArrayList<Song> 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) { public static boolean enqueue(Song song) {
if (musicService != null) { if (musicService != null) {
if (getPlayingQueue().size() > 0) { if (getPlayingQueue().size() > 0) {

View file

@ -2,6 +2,7 @@ package com.kabouzeid.gramophone.helper.menu;
import android.content.Intent; import android.content.Intent;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -25,8 +26,8 @@ import com.kabouzeid.gramophone.util.NavigationUtil;
public class SongMenuHelper { public class SongMenuHelper {
public static final int MENU_RES = R.menu.menu_item_song; public static final int MENU_RES = R.menu.menu_item_song;
public static boolean handleMenuClick(@NonNull AppCompatActivity activity, @NonNull Song song, @NonNull MenuItem item) { public static boolean handleMenuClick(@NonNull FragmentActivity activity, @NonNull Song song, int menuItemId) {
switch (item.getItemId()) { switch (menuItemId) {
case R.id.action_set_as_ringtone: case R.id.action_set_as_ringtone:
MusicUtil.setRingtone(activity, song.id); MusicUtil.setRingtone(activity, song.id);
return true; return true;
@ -86,7 +87,7 @@ public class SongMenuHelper {
@Override @Override
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
return handleMenuClick(activity, getSong(), item); return handleMenuClick(activity, getSong(), item.getItemId());
} }
public abstract Song getSong(); public abstract Song getSong();

View file

@ -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<Song> 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;
}
}

View file

@ -653,6 +653,12 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
notifyChange(QUEUE_CHANGED); notifyChange(QUEUE_CHANGED);
} }
public void addSongs(int position, List<Song> songs) {
playingQueue.addAll(position, songs);
originalPlayingQueue.addAll(position, songs);
notifyChange(QUEUE_CHANGED);
}
public void addSongs(List<Song> songs) { public void addSongs(List<Song> songs) {
playingQueue.addAll(songs); playingQueue.addAll(songs);
originalPlayingQueue.addAll(songs); originalPlayingQueue.addAll(songs);

View file

@ -42,9 +42,9 @@ import com.kabouzeid.appthemehelper.common.ATHToolbarActivity;
import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper; import com.kabouzeid.appthemehelper.util.ToolbarContentTintHelper;
import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.SongFileAdapter; 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.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.interfaces.CabHolder;
import com.kabouzeid.gramophone.misc.WrappedAsyncTaskLoader; import com.kabouzeid.gramophone.misc.WrappedAsyncTaskLoader;
import com.kabouzeid.gramophone.model.Song; 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 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 PATH = "path";
protected static final String CRUMBS = "crumbs"; protected static final String CRUMBS = "crumbs";
@ -301,20 +296,58 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
if (file.isDirectory()) { if (file.isDirectory()) {
setCrumb(new BreadCrumbLayout.Crumb(file), true); setCrumb(new BreadCrumbLayout.Crumb(file), true);
} else { } else {
List<File> files = new LinkedList<>();
files.add(file.getParentFile());
FileFilter fileFilter = new FileFilter() { FileFilter fileFilter = new FileFilter() {
@Override @Override
public boolean accept(File pathname) { public boolean accept(File pathname) {
return !pathname.isDirectory() && getFileFilter().accept(pathname); return !pathname.isDirectory() && getFileFilter().accept(pathname);
} }
}; };
new ListSongsAsyncTask(getActivity(), file, new ListSongsAsyncTask.OnSongsListedCallback() {
new ListSongsAsyncTask(this, PLAY, file).execute(new ListSongsAsyncTask.LoadingInfo(files, fileFilter, getFileComparator())); @Override
public void onSongsListed(@NonNull ArrayList<Song> 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<File> files) {
final int itemId = item.getItemId();
new ListSongsAsyncTask(getActivity(), null, new ListSongsAsyncTask.OnSongsListedCallback() {
@Override
public void onSongsListed(@NonNull ArrayList<Song> songs, Object extra) {
SongsMenuHelper.handleMenuClick(getActivity(), songs, itemId);
}
}).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator()));
}
@Override @Override
public void onFileMenuClicked(final File file, View view) { public void onFileMenuClicked(final File file, View view) {
PopupMenu popupMenu = new PopupMenu(getActivity(), view); PopupMenu popupMenu = new PopupMenu(getActivity(), view);
@ -323,12 +356,30 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override @Override
public boolean onMenuItemClick(MenuItem item) { 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<Song> 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: case R.id.action_set_as_start_directory:
PreferenceUtil.getInstance(getActivity()).setStartDirectory(file); PreferenceUtil.getInstance(getActivity()).setStartDirectory(file);
Toast.makeText(getActivity(), String.format(getString(R.string.new_start_directory), file.getPath()), Toast.LENGTH_SHORT).show();
return true; return true;
case R.id.action_scan: 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 true;
} }
return false; return false;
@ -339,9 +390,32 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override @Override
public boolean onMenuItemClick(MenuItem item) { 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<Song> 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: 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 true;
} }
return false; return false;
@ -351,23 +425,10 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
popupMenu.show(); popupMenu.show();
} }
private void scan(File file) { private ArrayList<File> toList(File file) {
new ScanFilesAsyncTask(this, file).execute(getFileFilter()); ArrayList<File> files = new ArrayList<>(1);
} files.add(file);
return files;
@Override
public void onAddToPlaylist(ArrayList<File> files) {
new ListSongsAsyncTask(this, ADD_TO_PLAYLIST, null).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator()));
}
@Override
public void onAddToCurrentPlaying(ArrayList<File> files) {
new ListSongsAsyncTask(this, ADD_TO_CURRENT_PLAYING, null).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator()));
}
@Override
public void onDeleteFromDevice(ArrayList<File> files) {
new ListSongsAsyncTask(this, DELETE, null).execute(new ListSongsAsyncTask.LoadingInfo(files, getFileFilter(), getFileComparator()));
} }
Comparator<File> fileComparator = new Comparator<File>() { Comparator<File> fileComparator = new Comparator<File>() {
@ -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<Toast> toastWeakReference;
private final WeakReference<Activity> 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<File> files) { private void updateAdapter(@NonNull List<File> files) {
adapter.swapDataSet(files); adapter.swapDataSet(files);
BreadCrumbLayout.Crumb crumb = getActiveCrumb(); BreadCrumbLayout.Crumb crumb = getActiveCrumb();
@ -470,60 +585,23 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
} }
} }
private void onSongsListed(int requestCode, ArrayList<Song> 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<ListSongsAsyncTask.LoadingInfo, Void, ArrayList<Song>> { private static class ListSongsAsyncTask extends DialogAsyncTask<ListSongsAsyncTask.LoadingInfo, Void, ArrayList<Song>> {
private WeakReference<FoldersFragment> fragmentWeakReference; private WeakReference<Context> contextWeakReference;
private final int requestCode; private WeakReference<OnSongsListedCallback> callbackWeakReference;
private final Object extra; private final Object extra;
public ListSongsAsyncTask(FoldersFragment foldersFragment, int requestCode, Object extra) { public ListSongsAsyncTask(Context context, Object extra, OnSongsListedCallback callback) {
super(foldersFragment.getActivity(), R.string.listing_files); super(context, R.string.listing_files);
this.requestCode = requestCode;
this.extra = extra; this.extra = extra;
fragmentWeakReference = new WeakReference<>(foldersFragment); contextWeakReference = new WeakReference<>(context);
callbackWeakReference = new WeakReference<>(callback);
} }
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
checkFragmentReference(); checkCallbackReference();
checkContextReference();
} }
@Override @Override
@ -532,14 +610,16 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
LoadingInfo info = params[0]; LoadingInfo info = params[0];
List<File> files = FileUtil.listFilesDeep(info.files, info.fileFilter); List<File> 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); Collections.sort(files, info.fileComparator);
FoldersFragment fragment = checkFragmentReference(); Context context = checkContextReference();
if (isCancelled() || fragment == null) return null; if (isCancelled() || context == null || checkCallbackReference() == null)
return null;
return FileUtil.matchFilesWithMediaStore(fragment.getActivity(), files); return FileUtil.matchFilesWithMediaStore(context, files);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
cancel(false); cancel(false);
@ -550,9 +630,25 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
@Override @Override
protected void onPostExecute(ArrayList<Song> songs) { protected void onPostExecute(ArrayList<Song> songs) {
super.onPostExecute(songs); super.onPostExecute(songs);
FoldersFragment fragment = checkFragmentReference(); OnSongsListedCallback callback = checkCallbackReference();
if (!songs.isEmpty() && fragment != null) if (songs != null && callback != null && !songs.isEmpty())
fragment.onSongsListed(requestCode, songs, extra); 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 { public static class LoadingInfo {
@ -567,118 +663,57 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
} }
} }
/** public interface OnSongsListedCallback {
* @return true if the task was canceled void onSongsListed(@NonNull ArrayList<Song> songs, Object extra);
*/
private FoldersFragment checkFragmentReference() {
FoldersFragment fragment = fragmentWeakReference.get();
if (fragment == null) {
cancel(false);
}
return fragment;
} }
} }
private void onScanPaths(final String[] toBeScanned) { private static class ListPathsAsyncTask extends DialogAsyncTask<ListPathsAsyncTask.LoadingInfo, String, String[]> {
if (getActivity() == null) return; private WeakReference<OnPathsListedCallback> onPathsListedCallbackWeakReference;
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 { public ListPathsAsyncTask(Context context, OnPathsListedCallback callback) {
int scanned = 0; super(context, R.string.listing_files);
int failed = 0; onPathsListedCallbackWeakReference = new WeakReference<>(callback);
private final String[] toBeScanned;
private final String scannedFiles;
private final String couldNotScanFiles;
private final WeakReference<Toast> toastWeakReference;
private final WeakReference<Activity> 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<FileFilter, String, String[]> {
private final File file;
private WeakReference<FoldersFragment> fragmentWeakReference;
public ScanFilesAsyncTask(FoldersFragment foldersFragment, File file) {
super(foldersFragment.getActivity(), R.string.listing_files);
this.file = file;
fragmentWeakReference = new WeakReference<>(foldersFragment);
} }
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
checkFragmentReference(); checkCallbackReference();
} }
@Override @Override
protected String[] doInBackground(FileFilter... params) { protected String[] doInBackground(LoadingInfo... params) {
try { try {
if (isCancelled() || checkFragmentReference() == null) return null; if (isCancelled() || checkCallbackReference() == null) return null;
final String[] toBeScanned; LoadingInfo info = params[0];
if (file.isDirectory()) { final String[] paths;
List<File> files = FileUtil.listFilesDeep(file, params[0]);
if (isCancelled() || checkFragmentReference() == null) return null; if (info.file.isDirectory()) {
List<File> 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++) { for (int i = 0; i < files.size(); i++) {
File f = files.get(i); File f = files.get(i);
try { 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) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
toBeScanned[i] = f.getPath(); paths[i] = f.getPath();
} }
if (isCancelled() || checkFragmentReference() == null) return toBeScanned; if (isCancelled() || checkCallbackReference() == null) return paths;
} }
} else { } else {
toBeScanned = new String[1]; paths = new String[1];
toBeScanned[0] = file.getPath(); paths[0] = info.file.getPath();
} }
return toBeScanned; return paths;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
cancel(false); cancel(false);
@ -687,20 +722,34 @@ public class FoldersFragment extends AbsMainActivityFragment implements MainActi
} }
@Override @Override
protected void onPostExecute(String[] toBeScanned) { protected void onPostExecute(String[] paths) {
super.onPostExecute(toBeScanned); super.onPostExecute(paths);
FoldersFragment fragment = checkFragmentReference(); OnPathsListedCallback callback = checkCallbackReference();
if (fragment != null) { if (callback != null) {
fragment.onScanPaths(toBeScanned); callback.onPathsListed(paths);
} }
} }
private FoldersFragment checkFragmentReference() { private OnPathsListedCallback checkCallbackReference() {
FoldersFragment fragment = fragmentWeakReference.get(); OnPathsListedCallback callback = onPathsListedCallbackWeakReference.get();
if (fragment == null) { if (callback == null) {
cancel(false); 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);
} }
} }

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24dp">
<path
android:fillColor="#FFFFFF"
android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z" />
</vector>

View file

@ -3,15 +3,21 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_add_to_playlist" android:id="@+id/action_play_next"
android:icon="@drawable/ic_playlist_add_white_24dp" android:icon="@drawable/ic_redo_white_24dp"
android:title="@string/action_add_to_playlist" android:title="@string/action_play_next"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
<item <item
android:id="@+id/action_add_to_current_playing" android:id="@+id/action_add_to_current_playing"
android:icon="@drawable/ic_library_add_white_24dp" android:icon="@drawable/ic_library_add_white_24dp"
android:title="@string/action_add_to_playing_queue" android:title="@string/action_add_to_playing_queue"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
<item
android:id="@+id/action_add_to_playlist"
android:icon="@drawable/ic_playlist_add_white_24dp"
android:title="@string/action_add_to_playlist"
app:showAsAction="always" />
</menu> </menu>

View file

@ -1,6 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_play_next"
android:title="@string/action_play_next"
app:showAsAction="never" />
<item
android:id="@+id/action_add_to_current_playing"
android:title="@string/action_add_to_playing_queue"
app:showAsAction="never" />
<item
android:id="@+id/action_add_to_playlist"
android:title="@string/action_add_to_playlist"
app:showAsAction="never" />
<item <item
android:id="@+id/action_set_as_start_directory" android:id="@+id/action_set_as_start_directory"
android:title="@string/action_set_as_start_directory" android:title="@string/action_set_as_start_directory"
@ -9,4 +21,8 @@
android:id="@+id/action_scan" android:id="@+id/action_scan"
android:title="@string/action_scan" android:title="@string/action_scan"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/action_delete_from_device"
android:title="@string/action_delete_from_device"
app:showAsAction="never" />
</menu> </menu>

View file

@ -1,8 +1,48 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_play_next"
android:title="@string/action_play_next"
app:showAsAction="never" />
<item
android:id="@+id/action_add_to_current_playing"
android:title="@string/action_add_to_playing_queue"
app:showAsAction="never" />
<item
android:id="@+id/action_add_to_playlist"
android:title="@string/action_add_to_playlist"
app:showAsAction="never" />
<item
android:id="@+id/action_go_to_album"
android:title="@string/action_go_to_album"
app:showAsAction="never" />
<item
android:id="@+id/action_go_to_artist"
android:title="@string/action_go_to_artist"
app:showAsAction="never" />
<item
android:id="@+id/action_share"
android:title="@string/action_share"
app:showAsAction="never" />
<item
android:id="@+id/action_tag_editor"
android:title="@string/action_tag_editor"
app:showAsAction="never" />
<item
android:id="@+id/action_details"
android:title="@string/action_details"
app:showAsAction="never" />
<item <item
android:id="@+id/action_scan" android:id="@+id/action_scan"
android:title="@string/action_scan" android:title="@string/action_scan"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/action_set_as_ringtone"
android:title="@string/action_set_as_ringtone"
app:showAsAction="never" />
<item
android:id="@+id/action_delete_from_device"
android:title="@string/action_delete_from_device"
app:showAsAction="never" />
</menu> </menu>

View file

@ -3,21 +3,27 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_add_to_playlist" android:id="@+id/action_play_next"
android:icon="@drawable/ic_playlist_add_white_24dp" android:icon="@drawable/ic_redo_white_24dp"
android:title="@string/action_add_to_playlist" android:title="@string/action_play_next"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
<item <item
android:id="@+id/action_add_to_current_playing" android:id="@+id/action_add_to_current_playing"
android:icon="@drawable/ic_library_add_white_24dp" android:icon="@drawable/ic_library_add_white_24dp"
android:title="@string/action_add_to_playing_queue" android:title="@string/action_add_to_playing_queue"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
<item
android:id="@+id/action_add_to_playlist"
android:icon="@drawable/ic_playlist_add_white_24dp"
android:title="@string/action_add_to_playlist"
app:showAsAction="always" />
<item <item
android:id="@+id/action_delete_from_device" android:id="@+id/action_delete_from_device"
android:icon="@drawable/ic_delete_white_24dp" android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/action_delete_from_device" android:title="@string/action_delete_from_device"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
</menu> </menu>

View file

@ -1,23 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_add_to_playlist" android:id="@+id/action_play_next"
android:icon="@drawable/ic_playlist_add_white_24dp" android:icon="@drawable/ic_redo_white_24dp"
android:title="@string/action_add_to_playlist" android:title="@string/action_play_next"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
<item <item
android:id="@+id/action_add_to_current_playing" android:id="@+id/action_add_to_current_playing"
android:icon="@drawable/ic_library_add_white_24dp" android:icon="@drawable/ic_library_add_white_24dp"
android:title="@string/action_add_to_playing_queue" android:title="@string/action_add_to_playing_queue"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
<item
android:id="@+id/action_add_to_playlist"
android:icon="@drawable/ic_playlist_add_white_24dp"
android:title="@string/action_add_to_playlist"
app:showAsAction="always" />
<item <item
android:id="@+id/action_delete_playlist" android:id="@+id/action_delete_playlist"
android:icon="@drawable/ic_delete_white_24dp" android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/delete_playlists_title" android:title="@string/delete_playlists_title"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
</menu> </menu>

View file

@ -1,23 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/action_add_to_playlist" android:id="@+id/action_play_next"
android:icon="@drawable/ic_playlist_add_white_24dp" android:icon="@drawable/ic_redo_white_24dp"
android:title="@string/action_add_to_playlist" android:title="@string/action_play_next"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
<item <item
android:id="@+id/action_add_to_current_playing" android:id="@+id/action_add_to_current_playing"
android:icon="@drawable/ic_library_add_white_24dp" android:icon="@drawable/ic_library_add_white_24dp"
android:title="@string/action_add_to_playing_queue" android:title="@string/action_add_to_playing_queue"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
<item
android:id="@+id/action_add_to_playlist"
android:icon="@drawable/ic_playlist_add_white_24dp"
android:title="@string/action_add_to_playlist"
app:showAsAction="always" />
<item <item
android:id="@+id/action_remove_from_playlist" android:id="@+id/action_remove_from_playlist"
android:icon="@drawable/ic_delete_white_24dp" android:icon="@drawable/ic_delete_white_24dp"
android:title="@string/action_remove_from_playlist" android:title="@string/action_remove_from_playlist"
app:showAsAction="ifRoom" /> app:showAsAction="always" />
</menu> </menu>

View file

@ -245,4 +245,5 @@
<string name="scanned_files">Scanned %1$d of %2$d files.</string> <string name="scanned_files">Scanned %1$d of %2$d files.</string>
<string name="could_not_scan_files">Could not scan %d files.</string> <string name="could_not_scan_files">Could not scan %d files.</string>
<string name="listing_files">Listing files</string> <string name="listing_files">Listing files</string>
<string name="new_start_directory">%s is the new start directory.</string>
</resources> </resources>