Playlists [beta]

This commit is contained in:
Karim Abou Zeid 2015-03-17 15:09:44 +01:00
commit de15a34365
33 changed files with 896 additions and 56 deletions

View file

@ -52,12 +52,13 @@ dependencies {
compile 'com.nhaarman.listviewanimations:lib-core:3.1.0@aar'
compile 'com.nhaarman.listviewanimations:lib-manipulation:3.1.0@aar'
compile 'com.nhaarman.listviewanimations:lib-core-slh:3.1.0@aar'
compile 'com.nineoldandroids:library:2.4.0+'
compile 'com.nineoldandroids:library:2.4.0'
compile 'com.melnykov:floatingactionbutton:1.2.0'
compile 'com.github.ksoichiro:android-observablescrollview:1.5.0'
compile 'com.mcxiaoke.volley:library:1.0.+'
compile 'com.squareup.picasso:picasso:2.5.0'
compile 'com.squareup:otto:1.3.6'
compile 'com.squareup.okhttp:okhttp:2.2.0'
compile('com.crashlytics.sdk.android:crashlytics:2.2.1@aar') {
transitive = true;
}

View file

@ -70,6 +70,11 @@
android:name="android.appwidget.provider"
android:resource="@xml/music_player_widget_info" />
</receiver>
<activity
android:name=".ui.activities.PlaylistDetailActivity"
android:label="@string/title_activity_playlist_detail" >
</activity>
</application>
</manifest>

View file

@ -12,6 +12,7 @@ import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.squareup.otto.Bus;
import com.squareup.otto.ThreadEnforcer;
import com.squareup.picasso.Picasso;
import io.fabric.sdk.android.Fabric;
@ -31,6 +32,7 @@ public class App extends Application {
super.onCreate();
Fabric.with(this, new Crashlytics());
MusicPlayerRemote.init(this);
//Picasso.with(this).setIndicatorsEnabled(true);// debug only
}
public int getAppTheme() {

View file

@ -30,7 +30,7 @@ public class PlayingQueueAdapter extends ArrayAdapter<Song> {
private Activity activity;
public PlayingQueueAdapter(Activity activity, List<Song> playList) {
super(activity, R.layout.item_list_playlist, playList);
super(activity, R.layout.item_list_playlist_song, playList);
this.activity = activity;
}
@ -38,7 +38,7 @@ public class PlayingQueueAdapter extends ArrayAdapter<Song> {
public View getView(final int position, View convertView, ViewGroup parent) {
final Song song = getItem(position);
if (convertView == null) {
convertView = LayoutInflater.from(activity).inflate(R.layout.item_list_playlist, parent, false);
convertView = LayoutInflater.from(activity).inflate(R.layout.item_list_playlist_song, parent, false);
}
final TextView title = (TextView) convertView.findViewById(R.id.song_title);
final ImageView playingIndicator = (ImageView) convertView.findViewById(R.id.playing_indicator);

View file

@ -0,0 +1,63 @@
package com.kabouzeid.gramophone.adapter;
import android.app.Activity;
import android.support.v4.util.Pair;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.model.Playlist;
import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity;
import com.kabouzeid.gramophone.util.NavigationUtil;
import java.util.List;
/**
* Created by karim on 16.03.15.
*/
public class PlaylistAdapter extends RecyclerView.Adapter<PlaylistAdapter.ViewHolder> {
public static final String TAG = PlaylistAdapter.class.getSimpleName();
protected Activity activity;
protected List<Playlist> dataSet;
public PlaylistAdapter(Activity activity, List<Playlist> objects) {
this.activity = activity;
dataSet = objects;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(activity).inflate(R.layout.item_list_playlist, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.playlistName.setText(dataSet.get(position).playlistName);
}
@Override
public int getItemCount() {
return dataSet.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
public TextView playlistName;
public ViewHolder(View itemView) {
super(itemView);
playlistName = (TextView) itemView.findViewById(R.id.playlist_name);
itemView.setOnClickListener(this);
}
@Override
public void onClick(View view) {
Pair[] sharedViews = null;
if (activity instanceof AbsFabActivity) sharedViews = ((AbsFabActivity)activity).getSharedViewsWithFab(sharedViews);
NavigationUtil.goToPlaylist(activity, dataSet.get(getPosition()).id, sharedViews);
}
}
}

View file

@ -0,0 +1,142 @@
package com.kabouzeid.gramophone.adapter.songadapter;
import android.app.Activity;
import android.content.Intent;
import android.support.v4.util.Pair;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.helper.SongDetailDialogHelper;
import com.kabouzeid.gramophone.loader.SongFilePathLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.Playlist;
import com.kabouzeid.gramophone.model.PlaylistSong;
import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity;
import com.kabouzeid.gramophone.ui.activities.tageditor.SongTagEditorActivity;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.NavigationUtil;
import com.kabouzeid.gramophone.util.PlaylistsUtil;
import com.squareup.picasso.Picasso;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 27.11.14.
*/
public class PlaylistSongAdapter extends RecyclerView.Adapter<PlaylistSongAdapter.ViewHolder> {
public static final String TAG = AlbumSongAdapter.class.getSimpleName();
protected Activity activity;
protected List<PlaylistSong> dataSet;
public PlaylistSongAdapter(Activity activity, List<PlaylistSong> objects) {
this.activity = activity;
dataSet = objects;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(activity).inflate(R.layout.item_list_song, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
final Song song = dataSet.get(position);
holder.songTitle.setText(song.title);
holder.songInfo.setText(song.artistName);
Picasso.with(activity)
.load(MusicUtil.getAlbumArtUri(song.albumId))
.placeholder(R.drawable.default_album_art)
.into(holder.albumArt);
}
@Override
public int getItemCount() {
return dataSet.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView songTitle;
TextView songInfo;
ImageView overflowButton;
ImageView albumArt;
public ViewHolder(View itemView) {
super(itemView);
songTitle = (TextView) itemView.findViewById(R.id.song_title);
songInfo = (TextView) itemView.findViewById(R.id.song_info);
albumArt = (ImageView) itemView.findViewById(R.id.album_art);
overflowButton = (ImageView) itemView.findViewById(R.id.menu);
overflowButton.setOnClickListener(this);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MusicPlayerRemote.openQueue((List<Song>) (List)dataSet, getAdapterPosition(), true);
}
});
}
@Override
public void onClick(View v) {
PopupMenu popupMenu = new PopupMenu(activity, v);
popupMenu.inflate(R.menu.menu_playlist_song);
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_delete_from_playlist:
int position = getAdapterPosition();
PlaylistsUtil.removeFromPlaylist(activity, dataSet.remove(position));
notifyItemRemoved(position);
return true;
case R.id.action_play_next:
MusicPlayerRemote.playNext(dataSet.get(getAdapterPosition()));
return true;
case R.id.action_add_to_current_playing:
MusicPlayerRemote.enqueue(dataSet.get(getAdapterPosition()));
return true;
case R.id.action_tag_editor:
Intent intent = new Intent(activity, SongTagEditorActivity.class);
intent.putExtra(AppKeys.E_ID, dataSet.get(getAdapterPosition()).id);
activity.startActivity(intent);
return true;
case R.id.action_details:
String songFilePath = SongFilePathLoader.getSongFilePath(activity, dataSet.get(getPosition()).id);
File songFile = new File(songFilePath);
SongDetailDialogHelper.getDialog(activity, songFile).show();
return true;
case R.id.action_go_to_album:
Pair[] albumPairs = new Pair[]{
Pair.create(albumArt, activity.getResources().getString(R.string.transition_album_cover))
};
if (activity instanceof AbsFabActivity)
albumPairs = ((AbsFabActivity) activity).getSharedViewsWithFab(albumPairs);
NavigationUtil.goToAlbum(activity, dataSet.get(getAdapterPosition()).albumId, albumPairs);
return true;
case R.id.action_go_to_artist:
Pair[] artistPairs = null;
if (activity instanceof AbsFabActivity)
artistPairs = ((AbsFabActivity) activity).getSharedViewsWithFab(artistPairs);
NavigationUtil.goToArtist(activity, dataSet.get(getAdapterPosition()).artistId, artistPairs);
return true;
}
return false;
}
});
popupMenu.show();
}
}
}

View file

@ -230,6 +230,12 @@ public class MusicPlayerRemote {
}
}
public static void removeFromQueue(Song song) {
if (musicService != null) {
musicService.removeSong(song);
}
}
public static void removeFromQueue(int position) {
if (musicService != null) {
musicService.removeSong(position);

View file

@ -0,0 +1,57 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.PlaylistsColumns;
import com.kabouzeid.gramophone.model.Playlist;
import java.util.ArrayList;
import java.util.List;
public class PlaylistLoader {
public static Playlist getPlaylist(final Context context, final int playlistID) {
Playlist playlist = new Playlist();
Cursor cursor = makePlaylistCursor(context, BaseColumns._ID + "=" + playlistID);
if (cursor != null && cursor.moveToFirst()) {
final int id = cursor.getInt(0);
final String name = cursor.getString(1);
playlist = new Playlist(id, name);
}
if (cursor != null) {
cursor.close();
}
return playlist;
}
public static List<Playlist> getAllPlaylists(final Context context) {
List<Playlist> playlists = new ArrayList<>();
Cursor cursor = makePlaylistCursor(context, null);
if (cursor != null && cursor.moveToFirst()) {
do {
final int id = cursor.getInt(0);
final String name = cursor.getString(1);
final Playlist playlist = new Playlist(id, name);
playlists.add(playlist);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return playlists;
}
public static Cursor makePlaylistCursor(final Context context, final String selection) {
return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
BaseColumns._ID,
/* 1 */
PlaylistsColumns.NAME
}, selection, null, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER);
}
}

View file

@ -0,0 +1,69 @@
package com.kabouzeid.gramophone.loader;
import android.content.Context;
import android.database.Cursor;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AudioColumns;
import com.kabouzeid.gramophone.model.Playlist;
import com.kabouzeid.gramophone.model.PlaylistSong;
import com.kabouzeid.gramophone.model.Song;
import java.util.ArrayList;
import java.util.List;
public class PlaylistSongLoader {
public static List<PlaylistSong> getPlaylistSongList(final Context context, final int playlistID) {
List<PlaylistSong> songs = new ArrayList<>();
Cursor cursor = makePlaylistSongCursor(context, playlistID);
if (cursor != null && cursor.moveToFirst()) {
do {
final int id = cursor.getInt(0);
final String songName = cursor.getString(1);
final String artist = cursor.getString(2);
final String album = cursor.getString(3);
final long duration = cursor.getLong(4);
final int trackNumber = cursor.getInt(5);
final int albumId = cursor.getInt(6);
final int artistId = cursor.getInt(7);
final int idInPlaylist = cursor.getInt(8);
final PlaylistSong song = new PlaylistSong(id, albumId, artistId, songName, artist, album, duration, trackNumber, playlistID, idInPlaylist);
songs.add(song);
} while (cursor.moveToNext());
}
if (cursor != null) {
cursor.close();
}
return songs;
}
public static Cursor makePlaylistSongCursor(final Context context, final int playlistID) {
return context.getContentResolver().query(
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistID),
new String[]{
/* 0 */
MediaStore.Audio.Playlists.Members.AUDIO_ID,
/* 1 */
AudioColumns.TITLE,
/* 2 */
AudioColumns.ARTIST,
/* 3 */
AudioColumns.ALBUM,
/* 4 */
AudioColumns.DURATION,
/* 5 */
AudioColumns.TRACK,
/* 6 */
AudioColumns.ALBUM_ID,
/* 7 */
AudioColumns.ARTIST_ID,
/* 8 */
MediaStore.Audio.Playlists.Members._ID
}, (AudioColumns.IS_MUSIC + "=1") + " AND " + AudioColumns.TITLE + " != ''", null,
MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
}
}

View file

@ -21,6 +21,7 @@ public final class AppKeys {
public static final String E_ALBUM = "com.kabouzeid.gramophone.ALBUM";
public static final String E_ARTIST = "com.kabouzeid.gramophone.ARTIST";
public static final String E_SONG = "com.kabouzeid.gramophone.SONG";
public static final String E_PLAYLIST = "com.kabouzeid.gramophone.PLAYLIST";
public static final String E_TAG_EDIT_MODE = "com.kabouzeid.gramophone.TAG_EDIT_MODE";
public static final String E_ID = "com.kabouzeid.gramophone.ID";
}

View file

@ -0,0 +1,16 @@
package com.kabouzeid.gramophone.model;
public class Playlist {
public int id;
public String playlistName;
public Playlist(final int id, final String playlistName) {
this.id = id;
this.playlistName = playlistName;
}
public Playlist() {
this.id = -1;
this.playlistName = "";
}
}

View file

@ -0,0 +1,19 @@
package com.kabouzeid.gramophone.model;
public class PlaylistSong extends Song{
public int playlistId;
public int idInPlayList;
public PlaylistSong(final int id, final int albumId, final int artistId, final String title, final String artistName,
final String albumName, final long duration, final int trackNumber, final int playlistId, final int idInPlayList) {
super(id, albumId, artistId, title, artistName, albumName, duration, trackNumber);
this.playlistId = playlistId;
this.idInPlayList = idInPlayList;
}
public PlaylistSong() {
super();
playlistId = -1;
id = -1;
}
}

View file

@ -499,6 +499,16 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe
saveState();
}
public void removeSong(Song song){
while (playingQueue.contains(song)){
playingQueue.remove(song);
}
while (originalPlayingQueue.contains(song)){
originalPlayingQueue.remove(song);
}
saveState();
}
public void moveSong(int from, int to) {
final int currentPosition = getPosition();
Song songToMove = playingQueue.remove(from);

View file

@ -36,6 +36,7 @@ import com.kabouzeid.gramophone.ui.fragments.NavigationDrawerFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.AlbumViewFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.ArtistViewFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.AbsMainActivityFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.PlaylistViewFragment;
import com.kabouzeid.gramophone.ui.fragments.mainactivityfragments.SongViewFragment;
import com.kabouzeid.gramophone.util.MusicUtil;
import com.kabouzeid.gramophone.util.NavigationUtil;
@ -261,41 +262,11 @@ public class MainActivity extends AbsFabActivity
super.onBackPressed();
}
public static class PlaceholderFragmentAbs extends AbsMainActivityFragment {
public PlaceholderFragmentAbs() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_place_holder, container, false);
TextView text = (TextView) rootView.findViewById(R.id.text);
text.setText("Coming soon!");
return rootView;
}
@Override
public void enableViews() {
}
@Override
public void disableViews() {
}
@Override
public boolean areViewsEnabled() {
return false;
}
}
private class MainActivityViewPagerAdapter extends FragmentPagerAdapter {
private String[] titles;
private SparseArray<AbsMainActivityFragment> pages; //TODO check if this must be static
private SparseArray<AbsMainActivityFragment> pages;
private Context context;
public MainActivityViewPagerAdapter(Activity activity) {
@ -306,7 +277,6 @@ public class MainActivity extends AbsFabActivity
context.getResources().getString(R.string.songs),
context.getResources().getString(R.string.albums),
context.getResources().getString(R.string.artists),
context.getResources().getString(R.string.genres),
context.getResources().getString(R.string.playlists)
};
}
@ -321,11 +291,9 @@ public class MainActivity extends AbsFabActivity
case 2:
return pages.get(position, new ArtistViewFragment());
case 3:
//TODO genres
case 4:
//TODO playlists
return pages.get(position, new PlaylistViewFragment());
}
return pages.get(position, new PlaceholderFragmentAbs());
return null;
}
@Override

View file

@ -0,0 +1,87 @@
package com.kabouzeid.gramophone.ui.activities;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.songadapter.PlaylistSongAdapter;
import com.kabouzeid.gramophone.loader.PlaylistLoader;
import com.kabouzeid.gramophone.loader.PlaylistSongLoader;
import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.model.Playlist;
import com.kabouzeid.gramophone.model.PlaylistSong;
import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity;
import com.kabouzeid.gramophone.util.NavigationUtil;
import java.util.List;
public class PlaylistDetailActivity extends AbsFabActivity {
public static final String TAG = PlaylistDetailActivity.class.getSimpleName();
private RecyclerView recyclerView;
private Playlist playlist;
@Override
protected void onCreate(Bundle savedInstanceState) {
setUpTranslucence(false, false);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playlist_detail);
getIntentExtras();
setUpToolBar();
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
List<PlaylistSong> songs = PlaylistSongLoader.getPlaylistSongList(this, playlist.id);
PlaylistSongAdapter adapter = new PlaylistSongAdapter(this, songs);
recyclerView.setLayoutManager(new GridLayoutManager(this, 1));
recyclerView.setAdapter(adapter);
}
private void getIntentExtras() {
Bundle intentExtras = getIntent().getExtras();
final int playlistId = intentExtras.getInt(AppKeys.E_PLAYLIST);
playlist = PlaylistLoader.getPlaylist(this, playlistId);
if (playlist == null) {
finish();
}
}
private void setUpToolBar() {
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
getSupportActionBar().setTitle(playlist.playlistName);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public String getTag() {
return TAG;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_playlist_detail, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
super.onBackPressed();
return true;
case R.id.action_settings:
return true;
case R.id.action_current_playing:
NavigationUtil.openCurrentPlayingIfPossible(this, getSharedViewsWithFab(null));
return true;
case R.id.action_playing_queue:
NavigationUtil.openPlayingQueueDialog(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}

View file

@ -138,7 +138,6 @@ public class NavigationDrawerFragment extends Fragment {
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.songs), R.drawable.ic_my_library_music_white_24dp));
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.albums), R.drawable.ic_album_white_24dp));
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.artists), R.drawable.ic_person_white_24dp));
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.genres), R.drawable.ic_my_library_music_white_24dp));
navigationDrawerItems.add(new NavigationDrawerItem(getString(R.string.playlists), R.drawable.ic_queue_music_white_24dp));
drawerAdapter = new NavigationDrawerItemAdapter(getActivity(), R.id.navigation_drawer, navigationDrawerItems);

View file

@ -0,0 +1,59 @@
package com.kabouzeid.gramophone.ui.fragments.mainactivityfragments;
import android.os.Bundle;
import android.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.adapter.PlaylistAdapter;
import com.kabouzeid.gramophone.adapter.songadapter.SongAdapter;
import com.kabouzeid.gramophone.loader.PlaylistLoader;
import com.kabouzeid.gramophone.loader.SongLoader;
import com.kabouzeid.gramophone.model.Playlist;
import com.kabouzeid.gramophone.model.Song;
import java.util.List;
public class PlaylistViewFragment extends AbsMainActivityFragment {
public static final String TAG = PlaylistViewFragment.class.getSimpleName();
private RecyclerView recyclerView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_playlist_view, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
setUpRecyclerView();
}
private void setUpRecyclerView() {
List<Playlist> playlists = PlaylistLoader.getAllPlaylists(getActivity());
PlaylistAdapter adapter = new PlaylistAdapter(getActivity(), playlists);
recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 1));
recyclerView.setAdapter(adapter);
recyclerView.setPadding(0, getTopPadding(), 0, getBottomPadding());
}
@Override
public void enableViews() {
super.enableViews();
recyclerView.setEnabled(true);
}
@Override
public void disableViews() {
super.disableViews();
recyclerView.setEnabled(false);
}
}

View file

@ -4,15 +4,24 @@ import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.util.Log;
import android.widget.Toast;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.loader.SongLoader;
import com.kabouzeid.gramophone.model.Song;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
/**
* Created by karim on 29.12.14.
@ -86,4 +95,57 @@ public class MusicUtil {
}
return albumArtDir;
}
public static void deleteTracks(final Context context, final List<Song> songs) {
final String[] projection = new String[] {
BaseColumns._ID, MediaStore.MediaColumns.DATA
};
final StringBuilder selection = new StringBuilder();
selection.append(BaseColumns._ID + " IN (");
for (int i = 0; i < songs.size(); i++) {
selection.append(songs.get(i).id);
if (i < songs.size() - 1) {
selection.append(",");
}
}
selection.append(")");
final Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
null, null);
if (cursor != null) {
// Step 1: Remove selected tracks from the current playlist, as well
// as from the album art cache
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
final int id = cursor.getInt(0);
final Song song = SongLoader.getSong(context, id);
MusicPlayerRemote.removeFromQueue(song);
}
// Step 2: Remove selected tracks from the database
context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
selection.toString(), null);
// Step 3: Remove files from card
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
final String name = cursor.getString(1);
final File f = new File(name);
try { // File.delete can throw a security exception
if (!f.delete()) {
// I'm not sure if we'd ever get here (deletion would
// have to fail, but no exception thrown)
Log.e("MusicUtils", "Failed to delete file " + name);
}
cursor.moveToNext();
} catch (final SecurityException ex) {
cursor.moveToNext();
}
}
cursor.close();
}
context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
Toast.makeText(context, "Deleted " + songs.size() + " songs", Toast.LENGTH_SHORT).show();
//TODO add resource string
}
}

View file

@ -16,6 +16,7 @@ import com.kabouzeid.gramophone.misc.AppKeys;
import com.kabouzeid.gramophone.ui.activities.AlbumDetailActivity;
import com.kabouzeid.gramophone.ui.activities.ArtistDetailActivity;
import com.kabouzeid.gramophone.ui.activities.MusicControllerActivity;
import com.kabouzeid.gramophone.ui.activities.PlaylistDetailActivity;
/**
* Created by karim on 12.03.15.
@ -61,6 +62,23 @@ public class NavigationUtil {
}
}
public static void goToPlaylist(final Activity activity, final int playlistId, final Pair[] sharedViews) {
if ((activity instanceof KabViewsDisableAble && ((KabViewsDisableAble) activity).areViewsEnabled()) || !(activity instanceof KabViewsDisableAble)) {
if (activity instanceof KabViewsDisableAble)
((KabViewsDisableAble) activity).disableViews();
final Intent intent = new Intent(activity, PlaylistDetailActivity.class);
intent.putExtra(AppKeys.E_PLAYLIST, playlistId);
if (sharedViews != null) {
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(activity,
sharedViews
);
ActivityCompat.startActivity(activity, intent, optionsCompat.toBundle());
} else {
activity.startActivity(intent);
}
}
}
public static void openCurrentPlayingIfPossible(final Activity activity, final Pair[] sharedViews) {
if (activity instanceof MusicControllerActivity) {
activity.onBackPressed();

View file

@ -0,0 +1,99 @@
package com.kabouzeid.gramophone.util;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.widget.Toast;
import com.kabouzeid.gramophone.model.PlaylistSong;
import com.kabouzeid.gramophone.model.Song;
import java.util.ArrayList;
import java.util.List;
/**
* Created by karim on 16.03.15.
*/
public class PlaylistsUtil {
public static int createPlaylist(final Context context, final String name) {
if (name != null && name.length() > 0) {
final ContentResolver resolver = context.getContentResolver();
final String[] projection = new String[]{
MediaStore.Audio.PlaylistsColumns.NAME
};
final String selection = MediaStore.Audio.PlaylistsColumns.NAME + " = '" + name + "'";
Cursor cursor = resolver.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
projection, selection, null, null);
if (cursor.getCount() <= 0) {
final ContentValues values = new ContentValues(1);
values.put(MediaStore.Audio.PlaylistsColumns.NAME, name);
final Uri uri = resolver.insert(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
values);
return Integer.parseInt(uri.getLastPathSegment());
}
cursor.close();
}
return -1;
}
public static void deletePlaylist(final Context context, final int playlistId) {
final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
context.getContentResolver().delete(uri, null, null);
}
public static void addToPlaylist(final Context context, final Song song, final long playlistId) {
List<Song> helperList = new ArrayList<>();
helperList.add(song);
addToPlaylist(context, helperList, playlistId);
}
public static void addToPlaylist(final Context context, final List<Song> songs, final long playlistId) {
final ContentResolver resolver = context.getContentResolver();
final String[] projection = new String[]{
MediaStore.Audio.PlaylistsColumns.NAME
};
final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
Cursor cursor = resolver.query(uri, projection, null, null, null);
cursor.moveToFirst();
final String playlistName = cursor.getString(0);
cursor.close();
ContentValues[] contentValues = new ContentValues[songs.size()];
for (int i = 0; i < songs.size(); i++) {
contentValues[i] = new ContentValues();
contentValues[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, songs.get(i).id);
}
resolver.bulkInsert(uri, contentValues);
Toast.makeText(context, "Added " + contentValues.length + " songs to playlist " + playlistName, Toast.LENGTH_SHORT).show();
//TODO add string resource
}
public static void removeFromPlaylist(final Context context, final PlaylistSong song) {
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
"external", song.playlistId);
String selection = MediaStore.Audio.Playlists.Members._ID+ " =?";
String[] selectionArgs = new String[]{String.valueOf(song.idInPlayList)};
context.getContentResolver().delete(uri, selection, selectionArgs);
}
public static void removeFromPlaylist(final Context context, final List<PlaylistSong> songs) {
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
"external", songs.get(0).playlistId);
String selectionArgs[] = new String[songs.size()];
for (int i = 0; i < selectionArgs.length; i++) {
selectionArgs[i] = String.valueOf(songs.get(i).idInPlayList);
}
String selection = MediaStore.Audio.Playlists.Members._ID + " in (";
for (String selectionArg : selectionArgs) selection += "?, ";
selection = selection.substring(0, selection.length() - 2) + ")";
context.getContentResolver().delete(uri, selection, selectionArgs);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 747 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,005 B

View file

@ -0,0 +1,43 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?colorPrimary"
android:elevation="@dimen/toolbar_elevation"
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:divider="@null"
android:dividerHeight="0px"
android:scrollbars="vertical"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:fitsSystemWindows="true">
<com.melnykov.fab.FloatingActionButton
android:id="@+id/fab"
style="@style/PlayPauseFab"
android:layout_margin="16dp"
/>
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:id="@+id/fragment_song_view"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:dividerHeight="0px"
android:divider="@null"
android:scrollbars="vertical"/>
</LinearLayout>

View file

@ -3,29 +3,28 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="64dp"
android:descendantFocusability="blocksDescendants"
android:background="?rect_selector"
android:orientation="horizontal"
>
android:paddingLeft="16dp"
android:paddingRight="16dp">
<TextView
android:id="@+id/song_title"
android:layout_width="0dp"
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:src="@drawable/ic_queue_music_white_24dp"
/>
<TextView
android:id="@+id/playlist_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:layout_marginRight="16dp"
android:fontFamily="sans-serif"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
<ImageView
android:id="@+id/playing_indicator"
style="@style/PlayingIndicator"
android:layout_marginRight="-8dp"
/>
<ImageView
android:id="@+id/menu"
style="@style/OverFlowButton"
android:layout_gravity="center_vertical"/>
</LinearLayout>

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="64dp"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal"
>
<TextView
android:id="@+id/song_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="16dp"
android:layout_weight="1"
android:fontFamily="sans-serif"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
<ImageView
android:id="@+id/playing_indicator"
style="@style/PlayingIndicator"
android:layout_marginRight="-8dp"
/>
<ImageView
android:id="@+id/menu"
style="@style/OverFlowButton"
android:layout_gravity="center_vertical"/>
</LinearLayout>

View file

@ -0,0 +1,20 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.kabouzeid.gramophone.ui.activities.PlaylistDetailActivity">
<item
android:id="@+id/action_current_playing"
android:icon="@drawable/music_box"
android:title="@string/action_current_playing"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_playing_queue"
android:icon="@drawable/ic_queue_music_white_24dp"
android:title="@string/action_playing_queue"
app:showAsAction="never"/>
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
</menu>

View file

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_delete_from_playlist"
android:title="@string/action_delete_from_playlist"
app:showAsAction="never"/>
<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_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
android:id="@+id/action_delete_from_disk"
android:title="@string/action_delete_from_disk"
app:showAsAction="never"/>
</menu>

View file

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View file

@ -74,5 +74,7 @@
<string name="added_title_to_playing_queue">Added 1 title to the playing queue.</string>
<string name="added_titles_to_playing_queue_1">Added </string>
<string name="added_titles_to_playing_queue_2"> titles to the playing queue.</string>
<string name="title_activity_playlist_detail">PlaylistDetailActivity</string>
<string name="action_delete_from_playlist">Delete from playlist</string>
</resources>