diff --git a/app/build.gradle b/app/build.gradle index 1679a730..5561c043 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -17,14 +17,14 @@ repositories { android { compileSdkVersion 22 - buildToolsVersion "21.1.2" + buildToolsVersion "22" defaultConfig { applicationId "com.kabouzeid.gramophone" minSdkVersion 16 targetSdkVersion 22 versionCode 1 - versionName "1.0" + versionName "0.9b" } compileOptions { diff --git a/app/manifest-merger-release-report.txt b/app/manifest-merger-release-report.txt index 22dce5e1..98c3d5f4 100644 --- a/app/manifest-merger-release-report.txt +++ b/app/manifest-merger-release-report.txt @@ -36,34 +36,37 @@ MERGED from com.crashlytics.sdk.android:crashlytics:2.2.1:9:5 ADDED from AndroidManifest.xml:9:22 application ADDED from AndroidManifest.xml:11:5 -MERGED from com.android.support:appcompat-v7:21.0.3:16:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 -MERGED from com.android.support:gridlayout-v7:21.0.3:16:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 -MERGED from com.android.support:recyclerview-v7:21.0.3:17:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 -MERGED from com.android.support:palette-v7:21.0.3:16:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 -MERGED from com.android.support:support-v13:21.0.3:16:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.afollestad:material-dialogs:0.6.4.1:12:5 +MERGED from com.android.support:appcompat-v7:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 +MERGED from com.android.support:recyclerview-v7:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 +MERGED from com.android.support:appcompat-v7:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 +MERGED from com.android.support:recyclerview-v7:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 +MERGED from com.android.support:gridlayout-v7:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 +MERGED from com.android.support:palette-v7:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 +MERGED from com.android.support:support-v13:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 +MERGED from com.android.support:cardview-v7:22.0.0:22:5 MERGED from com.nhaarman.listviewanimations:lib-core:3.1.0:26:5 MERGED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:26:5 MERGED from com.nhaarman.listviewanimations:lib-core-slh:3.1.0:26:5 MERGED from com.melnykov:floatingactionbutton:1.2.0:12:5 -MERGED from com.android.support:recyclerview-v7:21.0.3:17:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 -MERGED from com.android.support:recyclerview-v7:21.0.3:17:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 -MERGED from com.afollestad:material-dialogs:0.6.3.0:12:5 -MERGED from com.android.support:appcompat-v7:21.0.3:16:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.android.support:recyclerview-v7:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 +MERGED from com.android.support:recyclerview-v7:22.0.0:22:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 MERGED from com.crashlytics.sdk.android:crashlytics:2.2.1:11:5 MERGED from com.crashlytics.sdk.android:answers:1.1.1:9:5 MERGED from io.fabric.sdk.android:fabric:1.1.1:9:5 MERGED from io.fabric.sdk.android:fabric:1.1.1:9:5 MERGED from com.crashlytics.sdk.android:beta:1.1.1:9:5 MERGED from io.fabric.sdk.android:fabric:1.1.1:9:5 -MERGED from com.android.support:support-v4:21.0.3:16:5 +MERGED from com.android.support:support-v4:22.0.0:22:5 android:label ADDED from AndroidManifest.xml:15:9 android:allowBackup @@ -74,7 +77,7 @@ MERGED from com.android.support:support-v4:21.0.3:16:5 ADDED from AndroidManifest.xml:16:9 android:name ADDED from AndroidManifest.xml:12:9 -activity#gramophone.ui.activities.MainActivity +activity#com.kabouzeid.gramophone.ui.activities.MainActivity ADDED from AndroidManifest.xml:17:9 android:label ADDED from AndroidManifest.xml:19:13 @@ -90,27 +93,27 @@ category#android.intent.category.LAUNCHER ADDED from AndroidManifest.xml:23:17 android:name ADDED from AndroidManifest.xml:23:27 -activity#gramophone.ui.activities.AlbumDetailActivity +activity#com.kabouzeid.gramophone.ui.activities.AlbumDetailActivity ADDED from AndroidManifest.xml:26:9 android:name ADDED from AndroidManifest.xml:26:19 -activity#gramophone.ui.activities.ArtistDetailActivity +activity#com.kabouzeid.gramophone.ui.activities.ArtistDetailActivity ADDED from AndroidManifest.xml:28:9 android:name ADDED from AndroidManifest.xml:28:19 -activity#gramophone.ui.activities.MusicControllerActivity +activity#com.kabouzeid.gramophone.ui.activities.MusicControllerActivity ADDED from AndroidManifest.xml:30:9 android:parentActivityName ADDED from AndroidManifest.xml:32:13 android:name ADDED from AndroidManifest.xml:31:13 -service#gramophone.service.MusicService +service#com.kabouzeid.gramophone.service.MusicService ADDED from AndroidManifest.xml:35:9 android:enabled ADDED from AndroidManifest.xml:37:13 android:name ADDED from AndroidManifest.xml:36:13 -receiver#gramophone.service.MediaButtonIntentReceiver +receiver#com.kabouzeid.gramophone.service.MediaButtonIntentReceiver ADDED from AndroidManifest.xml:40:9 android:name ADDED from AndroidManifest.xml:40:19 @@ -126,7 +129,7 @@ ADDED from AndroidManifest.xml:46:9 ADDED from AndroidManifest.xml:48:13 android:name ADDED from AndroidManifest.xml:47:13 -activity#gramophone.ui.activities.tageditor.SongTagEditorActivity +activity#com.kabouzeid.gramophone.ui.activities.tageditor.SongTagEditorActivity ADDED from AndroidManifest.xml:50:9 android:label ADDED from AndroidManifest.xml:52:13 @@ -134,42 +137,67 @@ ADDED from AndroidManifest.xml:50:9 ADDED from AndroidManifest.xml:53:13 android:name ADDED from AndroidManifest.xml:51:13 -activity#gramophone.ui.activities.tageditor.AlbumTagEditorActivity +activity#com.kabouzeid.gramophone.ui.activities.tageditor.AlbumTagEditorActivity ADDED from AndroidManifest.xml:55:9 android:label ADDED from AndroidManifest.xml:57:13 android:name ADDED from AndroidManifest.xml:56:13 -activity#gramophone.ui.activities.SearchActivity +activity#com.kabouzeid.gramophone.ui.activities.SearchActivity ADDED from AndroidManifest.xml:59:9 android:label ADDED from AndroidManifest.xml:61:13 android:name ADDED from AndroidManifest.xml:60:13 +receiver#com.kabouzeid.gramophone.widget.MusicPlayerWidget +ADDED from AndroidManifest.xml:64:9 + android:name + ADDED from AndroidManifest.xml:64:19 +intent-filter#android.appwidget.action.APPWIDGET_UPDATE +ADDED from AndroidManifest.xml:65:13 +action#android.appwidget.action.APPWIDGET_UPDATE +ADDED from AndroidManifest.xml:66:17 + android:name + ADDED from AndroidManifest.xml:66:25 +meta-data#android.appwidget.provider +ADDED from AndroidManifest.xml:69:13 + android:resource + ADDED from AndroidManifest.xml:71:17 + android:name + ADDED from AndroidManifest.xml:70:17 +activity#com.kabouzeid.gramophone.ui.activities.PlaylistDetailActivity +ADDED from AndroidManifest.xml:74:9 + android:label + ADDED from AndroidManifest.xml:76:13 + android:name + ADDED from AndroidManifest.xml:75:13 uses-sdk INJECTED from AndroidManifest.xml:0:0 reason: use-sdk injection requested -MERGED from com.android.support:appcompat-v7:21.0.3:15:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 -MERGED from com.android.support:gridlayout-v7:21.0.3:15:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 -MERGED from com.android.support:recyclerview-v7:21.0.3:15:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 -MERGED from com.android.support:palette-v7:21.0.3:15:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 -MERGED from com.android.support:support-v13:21.0.3:15:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.afollestad:material-dialogs:0.6.4.1:8:5 +MERGED from com.android.support:appcompat-v7:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 +MERGED from com.android.support:recyclerview-v7:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 +MERGED from com.android.support:appcompat-v7:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 +MERGED from com.android.support:recyclerview-v7:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 +MERGED from com.android.support:gridlayout-v7:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 +MERGED from com.android.support:palette-v7:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 +MERGED from com.android.support:support-v13:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 +MERGED from com.android.support:cardview-v7:22.0.0:20:5 MERGED from com.nhaarman.listviewanimations:lib-core:3.1.0:22:5 MERGED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:22:5 MERGED from com.nhaarman.listviewanimations:lib-core-slh:3.1.0:22:5 MERGED from com.melnykov:floatingactionbutton:1.2.0:8:5 -MERGED from com.android.support:recyclerview-v7:21.0.3:15:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 -MERGED from com.github.ksoichiro:android-observablescrollview:1.3.0:21:5 -MERGED from com.android.support:recyclerview-v7:21.0.3:15:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 -MERGED from com.afollestad:material-dialogs:0.6.3.0:8:5 -MERGED from com.android.support:appcompat-v7:21.0.3:15:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.android.support:recyclerview-v7:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 +MERGED from com.github.ksoichiro:android-observablescrollview:1.5.0:21:5 +MERGED from com.android.support:recyclerview-v7:22.0.0:20:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 MERGED from com.crashlytics.sdk.android:crashlytics:2.2.1:7:5 MERGED from com.crashlytics.sdk.android:answers:1.1.1:7:5 MERGED from io.fabric.sdk.android:fabric:1.1.1:7:5 @@ -177,7 +205,7 @@ MERGED from io.fabric.sdk.android:fabric:1.1.1:7:5 MERGED from com.crashlytics.sdk.android:beta:1.1.1:7:5 MERGED from io.fabric.sdk.android:fabric:1.1.1:7:5 MERGED from asia.ivity.android:drag-sort-listview:1.0:6:5 -MERGED from com.android.support:support-v4:21.0.3:15:5 +MERGED from com.android.support:support-v4:22.0.0:20:5 android:targetSdkVersion INJECTED from AndroidManifest.xml:0:0 INJECTED from AndroidManifest.xml:0:0 @@ -185,13 +213,14 @@ MERGED from com.android.support:support-v4:21.0.3:15:5 INJECTED from AndroidManifest.xml:0:0 INJECTED from AndroidManifest.xml:0:0 activity#android.support.v7.widget.TestActivity -ADDED from com.android.support:recyclerview-v7:21.0.3:18:9 -MERGED from com.android.support:recyclerview-v7:21.0.3:18:9 -MERGED from com.android.support:recyclerview-v7:21.0.3:18:9 +ADDED from com.android.support:recyclerview-v7:22.0.0:23:9 +MERGED from com.android.support:recyclerview-v7:22.0.0:23:9 +MERGED from com.android.support:recyclerview-v7:22.0.0:23:9 +MERGED from com.android.support:recyclerview-v7:22.0.0:23:9 android:label - ADDED from com.android.support:recyclerview-v7:21.0.3:18:19 + ADDED from com.android.support:recyclerview-v7:22.0.0:25:13 android:name - ADDED from com.android.support:recyclerview-v7:21.0.3:18:60 + ADDED from com.android.support:recyclerview-v7:22.0.0:24:13 activity#com.nhaarman.listviewanimations.itemmanipulation.swipedismiss.SwipeTouchListenerTestActivity ADDED from com.nhaarman.listviewanimations:lib-manipulation:3.1.0:27:9 android:name diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/PlayingQueueAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/PlayingQueueAdapter.java index e02e838c..3b198a18 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/PlayingQueueAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/PlayingQueueAdapter.java @@ -10,8 +10,10 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; +import android.widget.Toast; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.helper.AddToPlaylistDialogHelper; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.SongDetailDialogHelper; import com.kabouzeid.gramophone.loader.SongFilePathLoader; @@ -62,6 +64,12 @@ public class PlayingQueueAdapter extends ArrayAdapter { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { + case R.id.action_delete_from_disk: + Toast.makeText(activity, "This feature is not available yet", Toast.LENGTH_SHORT).show(); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialogHelper.getDialog(activity, song).show(); + return true; case R.id.action_remove_from_playing_queue: MusicPlayerRemote.removeFromQueue(position); notifyDataSetChanged(); 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 11d85724..ccfd5fc5 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/PlaylistAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/PlaylistAdapter.java @@ -8,10 +8,14 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import com.afollestad.materialdialogs.MaterialDialog; +import com.kabouzeid.gramophone.App; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.model.DataBaseChangedEvent; import com.kabouzeid.gramophone.model.Playlist; import com.kabouzeid.gramophone.ui.activities.base.AbsFabActivity; import com.kabouzeid.gramophone.util.NavigationUtil; +import com.kabouzeid.gramophone.util.PlaylistsUtil; import java.util.List; @@ -36,7 +40,7 @@ public class PlaylistAdapter extends RecyclerView.Adapter { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { + case R.id.action_delete_from_disk: + Toast.makeText(activity, "This feature is not available yet", Toast.LENGTH_SHORT).show(); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialogHelper.getDialog(activity, song).show(); + return true; case R.id.action_play_next: MusicPlayerRemote.playNext(song); return true; diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/songadapter/PlaylistSongAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/songadapter/PlaylistSongAdapter.java index 20c651db..6a46a03c 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/songadapter/PlaylistSongAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/songadapter/PlaylistSongAdapter.java @@ -11,12 +11,15 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; +import android.widget.Toast; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.helper.AddToPlaylistDialogHelper; 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.misc.DragSortRecycler; import com.kabouzeid.gramophone.model.Playlist; import com.kabouzeid.gramophone.model.PlaylistSong; import com.kabouzeid.gramophone.model.Song; @@ -97,6 +100,12 @@ public class PlaylistSongAdapter extends RecyclerView.Adapter { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { + case R.id.action_delete_from_disk: + Toast.makeText(activity, "This feature is not available yet", Toast.LENGTH_SHORT).show(); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialogHelper.getDialog(activity, dataSet.get(getAdapterPosition())).show(); + return true; case R.id.action_play_next: MusicPlayerRemote.playNext(dataSet.get(getPosition())); return true; diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/AddToPlaylistDialogHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/AddToPlaylistDialogHelper.java new file mode 100644 index 00000000..4fed2e9a --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/AddToPlaylistDialogHelper.java @@ -0,0 +1,49 @@ +package com.kabouzeid.gramophone.helper; + +import android.content.Context; +import android.view.View; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.loader.PlaylistLoader; +import com.kabouzeid.gramophone.model.Playlist; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.util.PlaylistsUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 17.03.15. + */ +public class AddToPlaylistDialogHelper { + public static MaterialDialog getDialog(final Context context, final Song song) { + List tmpSong = new ArrayList<>(); + tmpSong.add(song); + return getDialog(context, tmpSong); + } + + public static MaterialDialog getDialog(final Context context, final List songs) { + final List playlists = PlaylistLoader.getAllPlaylists(context); + CharSequence[] playlistNames = new CharSequence[playlists.size() + 1]; + playlistNames[0] = context.getResources().getString(R.string.action_new_playlist); + for (int i = 1; i < playlistNames.length; i++) { + playlistNames[i] = playlists.get(i-1).name; + } + return new MaterialDialog.Builder(context) + .items(playlistNames) + .itemsCallback(new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog materialDialog, View view, int i, CharSequence charSequence) { + if (i == 0) { + materialDialog.dismiss(); + CreatePlaylistDialogHelper.getDialog(context, songs).show(); + } else { + materialDialog.dismiss(); + PlaylistsUtil.addToPlaylist(context, songs, playlists.get(i - 1).id); + } + } + }) + .build(); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/CreatePlaylistDialogHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/CreatePlaylistDialogHelper.java new file mode 100644 index 00000000..a96ae0df --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/CreatePlaylistDialogHelper.java @@ -0,0 +1,67 @@ +package com.kabouzeid.gramophone.helper; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.LinearLayout; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.util.PlaylistsUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by karim on 17.03.15. + */ +public class CreatePlaylistDialogHelper { + public static MaterialDialog getDialog(final Context context, final Song song) { + List tmpSong = new ArrayList<>(); + tmpSong.add(song); + return getDialog(context, tmpSong); + } + + public static MaterialDialog getDialog(final Context context, final List songs) { + final EditText editText = new EditText(context); + ViewGroup layout = (ViewGroup) LayoutInflater.from(context).inflate(R.layout.dialog_create_playlist, null); + if (editText.getParent() != null) {((ViewGroup) editText.getParent()).removeView(editText);} + layout.addView(editText, new LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + return new MaterialDialog.Builder(context) + .title(context.getResources().getString(R.string.action_new_playlist)) + .customView(layout, false) + .positiveText(context.getResources().getString(R.string.ok)) + .negativeText(context.getResources().getString(R.string.cancel)) + .callback(new MaterialDialog.ButtonCallback() { + @Override + public void onPositive(MaterialDialog dialog) { + super.onPositive(dialog); + final String playlistName = editText.getText().toString(); + if (!playlistName.trim().equals("")) { + dialog.dismiss(); + final int playlistId = PlaylistsUtil.createPlaylist(context, playlistName); + if (playlistId != -1) { + if (songs != null) { + PlaylistsUtil.addToPlaylist(context, songs, playlistId); + } + } + } + } + + @Override + public void onNegative(MaterialDialog dialog) { + super.onNegative(dialog); + dialog.dismiss(); + } + } + ) + .build(); + } + + public static MaterialDialog getDialog(final Context context) { + return getDialog(context, (List) null); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingQueueDialogHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingQueueDialogHelper.java index 270063a0..dafc2abe 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingQueueDialogHelper.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingQueueDialogHelper.java @@ -8,6 +8,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.adapter.PlayingQueueAdapter; import com.kabouzeid.gramophone.model.Song; +import com.kabouzeid.gramophone.util.PlaylistsUtil; import com.mobeta.android.dslv.DragSortListView; import java.util.List; @@ -16,8 +17,8 @@ import java.util.List; * Created by karim on 24.01.15. */ public class PlayingQueueDialogHelper { - public static MaterialDialog getDialog(Activity activity) { - List playingQueue = MusicPlayerRemote.getPlayingQueue(); + public static MaterialDialog getDialog(final Activity activity) { + final List playingQueue = MusicPlayerRemote.getPlayingQueue(); if (playingQueue.isEmpty()) { return null; } @@ -26,7 +27,7 @@ public class PlayingQueueDialogHelper { .title(activity.getResources().getString(R.string.label_current_playing_queue)) .customView(R.layout.dialog_playlist, false) .positiveText(activity.getResources().getString(R.string.close)) - .negativeText(activity.getResources().getString(R.string.save_as_playlist)) + .neutralText(activity.getResources().getString(R.string.save_as_playlist)) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { @@ -35,8 +36,10 @@ public class PlayingQueueDialogHelper { } @Override - public void onNegative(MaterialDialog dialog) { + public void onNeutral(MaterialDialog dialog) { super.onNegative(dialog); + dialog.dismiss(); + AddToPlaylistDialogHelper.getDialog(activity, playingQueue).show(); } }) .build(); diff --git a/app/src/main/java/com/kabouzeid/gramophone/lastfm/album/LastFMAlbumInfoUtil.java b/app/src/main/java/com/kabouzeid/gramophone/lastfm/album/LastFMAlbumInfoUtil.java index 4d1d9105..383c99dd 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/lastfm/album/LastFMAlbumInfoUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/lastfm/album/LastFMAlbumInfoUtil.java @@ -87,7 +87,7 @@ public class LastFMAlbumInfoUtil { public static void downloadAlbumInfoJSON(final Context context, final String album, final String artist, final Response.Listener callbackSuccess, final Response.ErrorListener callbackError) { App app = (App) context.getApplicationContext(); String albumUrl = LastFMAlbumInfoUtil.getAlbumUrl(album, artist); - JsonObjectRequest albumInfoJSONRequest = new JsonObjectRequest(0, albumUrl, null, new Response.Listener() { + JsonObjectRequest albumInfoJSONRequest = new JsonObjectRequest(0, albumUrl, (JSONObject)null, new Response.Listener() { @Override public void onResponse(JSONObject response) { LastFMAlbumInfoUtil.saveAlbumJSONDataToCacheAndDisk(context, album, artist, response); diff --git a/app/src/main/java/com/kabouzeid/gramophone/lastfm/artist/LastFMArtistBiographyLoader.java b/app/src/main/java/com/kabouzeid/gramophone/lastfm/artist/LastFMArtistBiographyLoader.java index 86255a2b..53358e11 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/lastfm/artist/LastFMArtistBiographyLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/lastfm/artist/LastFMArtistBiographyLoader.java @@ -38,7 +38,7 @@ public class LastFMArtistBiographyLoader { private static void downloadArtistBio(final Context context, final String artist, final ArtistBioLoaderCallback callback) { App app = (App) context.getApplicationContext(); String artistUrl = LastFMArtistInfoUtil.getArtistUrl(artist); - JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, null, new Response.Listener() { + JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, (JSONObject)null, new Response.Listener() { @Override public void onResponse(JSONObject response) { LastFMArtistInfoUtil.saveArtistJSONDataToCacheAndDisk(context, artist, response); diff --git a/app/src/main/java/com/kabouzeid/gramophone/lastfm/artist/LastFMArtistInfoUtil.java b/app/src/main/java/com/kabouzeid/gramophone/lastfm/artist/LastFMArtistInfoUtil.java index 4750fab5..c7dea915 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/lastfm/artist/LastFMArtistInfoUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/lastfm/artist/LastFMArtistInfoUtil.java @@ -1,7 +1,6 @@ package com.kabouzeid.gramophone.lastfm.artist; import android.content.Context; -import android.graphics.Bitmap; import android.net.Uri; import android.util.Log; @@ -101,7 +100,7 @@ public class LastFMArtistInfoUtil { public static void downloadArtistJSON(final Context context, final String artist, final Response.Listener callback) { App app = (App) context.getApplicationContext(); String artistUrl = LastFMArtistInfoUtil.getArtistUrl(artist); - JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, null, new Response.Listener() { + JsonObjectRequest artistInfoJSONRequest = new JsonObjectRequest(0, artistUrl, (JSONObject)null, new Response.Listener() { @Override public void onResponse(JSONObject response) { LastFMArtistInfoUtil.saveArtistJSONDataToCacheAndDisk(context, artist, response); diff --git a/app/src/main/java/com/kabouzeid/gramophone/misc/DragSortRecycler.java b/app/src/main/java/com/kabouzeid/gramophone/misc/DragSortRecycler.java new file mode 100644 index 00000000..9871cda6 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/misc/DragSortRecycler.java @@ -0,0 +1,469 @@ +/* + * DragSortRecycler + * + * Added drag and drop functionality to your RecyclerView + * + * + * Copyright 2014 Emile Belanger. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kabouzeid.gramophone.misc; + +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.support.annotation.Nullable; + +import java.lang.reflect.Modifier; + + +public class DragSortRecycler extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener { + + final String TAG = "DragSortRecycler"; + + final boolean DEBUG = false; + + private int dragHandleWidth = 0; + + private int selectedDragItemPos = -1; + + private int fingerAnchorY; + + private int fingerY; + + private int fingerOffsetInViewY; + + private float autoScrollWindow = 0.1f; + private float autoScrollSpeed = 0.5f; + + private BitmapDrawable floatingItem; + private Rect floatingItemStatingBounds; + private Rect floatingItemBounds; + + + private float floatingItemAlpha = 0.5f; + private int floatingItemBgColor = 0; + + private int viewHandleId = -1; + + + OnItemMovedListener moveInterface; + + private boolean isDragging; + @Nullable + OnDragStateChangedListener dragStateChangedListener; + + + + public interface OnItemMovedListener + { + public void onItemMoved(int from, int to); + } + + public interface OnDragStateChangedListener { + public void onDragStart(); + public void onDragStop(); + } + + private void debugLog(String log) + { + if (DEBUG) + Log.d(TAG, log); + } + + public RecyclerView.OnScrollListener getScrollListener() + { + return scrollListener; + } + + /* + * Set the item move interface + */ + public void setOnItemMovedListener(OnItemMovedListener swif) + { + moveInterface = swif; + } + + public void setViewHandleId(int id) + { + viewHandleId = id; + } + + public void setLeftDragArea(int w) + { + dragHandleWidth = w; + } + + public void setFloatingAlpha(float a) + { + floatingItemAlpha = a; + } + + public void setFloatingBgColor(int c) + { + floatingItemBgColor = c; + } + /* + Set the window at top and bottom of list, must be between 0 and 0.5 + For example 0.1 uses the top and bottom 10% of the lists for scrolling + */ + public void setAutoScrollWindow(float w) + { + autoScrollWindow = w; + } + + /* + Set the autoscroll speed, default is 0.5 + */ + public void setAutoScrollSpeed(float speed) + { + autoScrollSpeed = speed; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView rv, RecyclerView.State state) { + super.getItemOffsets(outRect, view, rv, state); + + debugLog("getItemOffsets"); + + debugLog("View top = " + view.getTop()); + if (selectedDragItemPos != -1) + { + int itemPos = rv.getChildPosition(view); + debugLog("itemPos =" + itemPos); + + if(!canDragOver(itemPos)) { + return; + } + + //Movement of finger + float totalMovement = fingerY-fingerAnchorY; + + if (itemPos == selectedDragItemPos) + { + view.setVisibility(View.INVISIBLE); + } + else + { + //Make view visible incase invisible + view.setVisibility(View.VISIBLE); + + //Find middle of the floatingItem + float floatMiddleY = floatingItemBounds.top + floatingItemBounds.height()/2; + + //Moving down the list + //These will auto-animate if the device continually sends touch motion events + // if (totalMovment>0) + { + if ((itemPos > selectedDragItemPos) && (view.getTop() < floatMiddleY)) + { + float amountUp = (floatMiddleY - view.getTop()) / (float)view.getHeight(); + // amountUp *= 0.5f; + if (amountUp > 1) + amountUp = 1; + + outRect.top = -(int)(floatingItemBounds.height()*amountUp); + outRect.bottom = (int)(floatingItemBounds.height()*amountUp); + } + + }//Moving up the list + // else if (totalMovment < 0) + { + if((itemPos < selectedDragItemPos) && (view.getBottom() > floatMiddleY)) + { + float amountDown = ((float)view.getBottom() - floatMiddleY) / (float)view.getHeight(); + // amountDown *= 0.5f; + if (amountDown > 1) + amountDown = 1; + + outRect.top = (int)(floatingItemBounds.height()*amountDown); + outRect.bottom = -(int)(floatingItemBounds.height()*amountDown); + } + } + } + } + else + { + outRect.top = 0; + outRect.bottom = 0; + //Make view visible incase invisible + view.setVisibility(View.VISIBLE); + } + } + + /** + * Find the new position by scanning through the items on + * screen and finding the positional relationship. + * This *seems* to work, another method would be to use + * getItemOffsets, but I think that could miss items?.. + */ + private int getNewPostion(RecyclerView rv) + { + int itemsOnScreen = rv.getLayoutManager().getChildCount(); + + float floatMiddleY = floatingItemBounds.top + floatingItemBounds.height()/2; + + int above=0; + int below = Integer.MAX_VALUE; + for (int n=0;n < itemsOnScreen;n++) //Scan though items on screen, however they may not + { // be in order! + + View view = rv.getLayoutManager().getChildAt(n); + + if (view.getVisibility() != View.VISIBLE) + continue; + + int itemPos = rv.getChildPosition(view); + + if (itemPos == selectedDragItemPos) //Don't check against itself! + continue; + + float viewMiddleY = view.getTop() + view.getHeight()/2; + if (floatMiddleY > viewMiddleY) //Is above this item + { + if (itemPos > above) + above = itemPos; + } + else if (floatMiddleY <= viewMiddleY) //Is below this item + { + if (itemPos < below) + below = itemPos; + } + } + debugLog("above = " + above + " below = " + below); + + if (below != Integer.MAX_VALUE) { + if (below < selectedDragItemPos) //Need to count itself + below++; + return below - 1; + } + else + { + if (above < selectedDragItemPos) + above++; + + return above; + } + } + + + @Override + public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { + debugLog("onInterceptTouchEvent"); + + //if (e.getAction() == MotionEvent.ACTION_DOWN) + { + View itemView = rv.findChildViewUnder(e.getX(), e.getY()); + + if (itemView==null) + return false; + + boolean dragging = false; + + if ((dragHandleWidth > 0 ) && (e.getX() < dragHandleWidth)) + { + dragging = true; + } + else if (viewHandleId != -1) + { + //Find the handle in the list item + View handleView = itemView.findViewById(viewHandleId); + + if (handleView == null) + { + Log.e(TAG, "The view ID " + viewHandleId + " was not found in the RecycleView item"); + return false; + } + + //View should be visible to drag + if(handleView.getVisibility()!=View.VISIBLE) { + return false; + } + + //We need to find the relative position of the handle to the parent view + //Then we can work out if the touch is within the handle + int[] parentItemPos = new int[2]; + itemView.getLocationInWindow(parentItemPos); + + int[] handlePos = new int[2]; + handleView.getLocationInWindow(handlePos); + + int xRel = handlePos[0] - parentItemPos[0]; + int yRel = handlePos[1] - parentItemPos[1]; + + Rect touchBounds = new Rect(itemView.getLeft() + xRel, itemView.getTop() + yRel, + itemView.getLeft() + xRel + handleView.getWidth(), + itemView.getTop() + yRel + handleView.getHeight() + ); + + if (touchBounds.contains((int)e.getX(), (int)e.getY())) + dragging = true; + + debugLog("parentItemPos = " + parentItemPos[0] + " " + parentItemPos[1]); + debugLog("handlePos = " + handlePos[0] + " " + handlePos[1]); + } + + + if (dragging) + { + debugLog("Started Drag"); + + setIsDragging(true); + + floatingItem = createFloatingBitmap(itemView); + + fingerAnchorY = (int)e.getY(); + fingerOffsetInViewY = fingerAnchorY - itemView.getTop(); + fingerY = fingerAnchorY; + + selectedDragItemPos = rv.getChildPosition(itemView); + debugLog("selectedDragItemPos = " + selectedDragItemPos); + + return true; + } + } + return false; + } + + @Override + public void onTouchEvent(RecyclerView rv, MotionEvent e) { + debugLog("onTouchEvent"); + + if ((e.getAction() == MotionEvent.ACTION_UP) || + (e.getAction() == MotionEvent.ACTION_CANCEL)) + { + if ((e.getAction() == MotionEvent.ACTION_UP) && selectedDragItemPos != -1) + { + int newPos = getNewPostion(rv); + if (moveInterface != null) + moveInterface.onItemMoved(selectedDragItemPos, newPos); + } + + setIsDragging(false); + selectedDragItemPos = -1; + floatingItem = null; + rv.invalidateItemDecorations(); + return; + } + + + fingerY = (int)e.getY(); + + if (floatingItem!=null) + { + floatingItemBounds.top = fingerY - fingerOffsetInViewY; + + if (floatingItemBounds.top < -floatingItemStatingBounds.height()/2) //Allow half the view out the top + floatingItemBounds.top = -floatingItemStatingBounds.height()/2; + + floatingItemBounds.bottom = floatingItemBounds.top + floatingItemStatingBounds.height(); + + floatingItem.setBounds(floatingItemBounds); + } + + //Do auto scrolling at end of list + float scrollAmount=0; + if (fingerY > (rv.getHeight() * (1-autoScrollWindow))) + { + scrollAmount = (fingerY - (rv.getHeight() * (1-autoScrollWindow))); + } + else if (fingerY < (rv.getHeight() * autoScrollWindow)) + { + scrollAmount = (fingerY - (rv.getHeight() * autoScrollWindow)); + } + debugLog("Scroll: " + scrollAmount); + + scrollAmount *= autoScrollSpeed; + rv.scrollBy(0, (int)scrollAmount); + + rv.invalidateItemDecorations();// Redraw + } + + private void setIsDragging(final boolean dragging) { + if(dragging != isDragging) { + isDragging = dragging; + if(dragStateChangedListener != null) { + if (isDragging) { + dragStateChangedListener.onDragStart(); + } else { + dragStateChangedListener.onDragStop(); + } + } + } + } + + public void setOnDragStateChangedListener(final OnDragStateChangedListener dragStateChangedListener) { + this.dragStateChangedListener = dragStateChangedListener; + } + + + Paint bgColor = new Paint(); + @Override + public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { + if (floatingItem != null) { + floatingItem.setAlpha((int)(255 * floatingItemAlpha)); + bgColor.setColor(floatingItemBgColor); + c.drawRect(floatingItemBounds,bgColor); + floatingItem.draw(c); + } + } + + RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + } + + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + debugLog("Scrolled: " + dx + " " + dy); + fingerAnchorY -= dy; + } + }; + + /** + * + * + * @param position + * @return True if we can drag the item over this position, False if not. + */ + protected boolean canDragOver(int position) { + return true; + } + + + private BitmapDrawable createFloatingBitmap(View v) + { + floatingItemStatingBounds = new Rect(v.getLeft(), v.getTop(),v.getRight(), v.getBottom()); + floatingItemBounds = new Rect(floatingItemStatingBounds); + + Bitmap bitmap = Bitmap.createBitmap(floatingItemStatingBounds.width(), + floatingItemStatingBounds.height(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + v.draw(canvas); + + BitmapDrawable retDrawable = new BitmapDrawable(v.getResources(), bitmap); + retDrawable.setBounds(floatingItemBounds); + + return retDrawable; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/model/DataBaseChangedEvent.java b/app/src/main/java/com/kabouzeid/gramophone/model/DataBaseChangedEvent.java new file mode 100644 index 00000000..cad9c41a --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/model/DataBaseChangedEvent.java @@ -0,0 +1,22 @@ +package com.kabouzeid.gramophone.model; + +/** + * Created by karim on 17.03.15. + */ +public class DataBaseChangedEvent { + public static final int PLAYLISTS_CHANGED = 0; + public static final int ALBUMS_CHANGED = 1; + public static final int ARTISTS_CHANGED = 2; + public static final int SONGS_CHANGED = 3; + public static final int DATABASE_CHANGED = 4; + + private int action; + + public DataBaseChangedEvent(int action) { + this.action = action; + } + + public int getAction() { + return action; + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/model/Playlist.java b/app/src/main/java/com/kabouzeid/gramophone/model/Playlist.java index 03f11de5..26e13c01 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/model/Playlist.java +++ b/app/src/main/java/com/kabouzeid/gramophone/model/Playlist.java @@ -2,15 +2,15 @@ package com.kabouzeid.gramophone.model; public class Playlist { public int id; - public String playlistName; + public String name; - public Playlist(final int id, final String playlistName) { + public Playlist(final int id, final String name) { this.id = id; - this.playlistName = playlistName; + this.name = name; } public Playlist() { this.id = -1; - this.playlistName = ""; + this.name = ""; } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java index 82f54182..7cba7547 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java @@ -15,6 +15,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import com.github.ksoichiro.android.observablescrollview.ObservableRecyclerView; import com.kabouzeid.gramophone.App; @@ -264,6 +265,7 @@ public class AlbumDetailActivity extends AbsFabActivity { NavigationUtil.openPlayingQueueDialog(this); return true; case R.id.action_settings: + Toast.makeText(this, "This feature is not available yet", Toast.LENGTH_SHORT).show(); return true; case R.id.action_current_playing: NavigationUtil.openCurrentPlayingIfPossible(this, getSharedViewsWithFab(null)); diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java index 07020b74..1baa26db 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java @@ -326,6 +326,7 @@ public class ArtistDetailActivity extends AbsFabActivity { Toast.makeText(ArtistDetailActivity.this, getResources().getString(R.string.updating), Toast.LENGTH_SHORT).show(); setUpArtistImageAndApplyPalette(true); case R.id.action_settings: + Toast.makeText(this, "This feature is not available yet", Toast.LENGTH_SHORT).show(); return true; case R.id.action_current_playing: NavigationUtil.openCurrentPlayingIfPossible(this, getSharedViewsWithFab(null)); diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java index 5180b7ab..1dea7d3f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java @@ -26,6 +26,7 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.google.samples.apps.iosched.ui.widget.SlidingTabLayout; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.helper.AboutDeveloperDialogHelper; +import com.kabouzeid.gramophone.helper.CreatePlaylistDialogHelper; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.PlayingQueueDialogHelper; import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble; @@ -58,6 +59,7 @@ public class MainActivity extends AbsFabActivity private MainActivityViewPagerAdapter viewPagerAdapter; private ViewPager viewPager; private SlidingTabLayout slidingTabLayout; + private int currentPage = -1; @Override protected void onCreate(Bundle savedInstanceState) { @@ -78,6 +80,7 @@ public class MainActivity extends AbsFabActivity viewPagerAdapter = new MainActivityViewPagerAdapter(this); viewPager.setAdapter(viewPagerAdapter); int startPosition = PreferenceUtils.getInstace(this).getStartPage(); + currentPage = startPosition; viewPager.setCurrentItem(startPosition); navigationDrawerFragment.setItemChecked(startPosition); @@ -93,6 +96,8 @@ public class MainActivity extends AbsFabActivity public void onPageSelected(final int position) { PreferenceUtils.getInstace(MainActivity.this).setStartPage(position); navigationDrawerFragment.setItemChecked(position); + currentPage = position; + invalidateOptionsMenu(); } @Override @@ -210,7 +215,14 @@ public class MainActivity extends AbsFabActivity @Override public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.drawer, menu); + switch (currentPage){ + case 3: + getMenuInflater().inflate(R.menu.menu_playlists, menu); + break; + default: + getMenuInflater().inflate(R.menu.drawer, menu); + break; + } restoreActionBar(); return true; } @@ -229,10 +241,17 @@ public class MainActivity extends AbsFabActivity } int id = item.getItemId(); switch (id) { + case R.id.action_licenses: + Toast.makeText(this, "This feature is not available yet", Toast.LENGTH_SHORT).show(); + return true; + case R.id.action_new_playlist: + CreatePlaylistDialogHelper.getDialog(this).show(); + return true; case R.id.action_search: startActivity(new Intent(MainActivity.this, SearchActivity.class)); return true; case R.id.action_settings: + Toast.makeText(this, "This feature is not available yet", Toast.LENGTH_SHORT).show(); return true; case R.id.action_about: AboutDeveloperDialogHelper.getDialog(this).show(); @@ -277,7 +296,7 @@ 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.playlists) + context.getResources().getString(R.string.playlists) + " BETA" }; } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MusicControllerActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MusicControllerActivity.java index 4dae85d4..4a528957 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MusicControllerActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MusicControllerActivity.java @@ -18,10 +18,9 @@ import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; -import com.afollestad.materialdialogs.MaterialDialog; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.helper.AddToPlaylistDialogHelper; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; -import com.kabouzeid.gramophone.helper.PlayingQueueDialogHelper; import com.kabouzeid.gramophone.helper.SongDetailDialogHelper; import com.kabouzeid.gramophone.lastfm.artist.LastFMArtistImageUrlLoader; import com.kabouzeid.gramophone.loader.SongFilePathLoader; @@ -388,6 +387,12 @@ public class MusicControllerActivity extends AbsFabActivity { public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); switch (id) { + case R.id.action_settings: + Toast.makeText(this, "This feature is not available yet", Toast.LENGTH_SHORT).show(); + return true; + case R.id.action_add_to_playlist: + AddToPlaylistDialogHelper.getDialog(this, song).show(); + return true; case android.R.id.home: super.onBackPressed(); return true; diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java index 070ecfbc..017d62db 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java @@ -12,10 +12,12 @@ 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.misc.DragSortRecycler; 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 com.kabouzeid.gramophone.util.PlaylistsUtil; import java.util.List; @@ -34,10 +36,27 @@ public class PlaylistDetailActivity extends AbsFabActivity { setUpToolBar(); recyclerView = (RecyclerView) findViewById(R.id.recycler_view); - List songs = PlaylistSongLoader.getPlaylistSongList(this, playlist.id); - PlaylistSongAdapter adapter = new PlaylistSongAdapter(this, songs); + final List songs = PlaylistSongLoader.getPlaylistSongList(this, playlist.id); + final PlaylistSongAdapter adapter = new PlaylistSongAdapter(this, songs); recyclerView.setLayoutManager(new GridLayoutManager(this, 1)); recyclerView.setAdapter(adapter); + + DragSortRecycler dragSortRecycler = new DragSortRecycler(); + dragSortRecycler.setViewHandleId(R.id.album_art); + + dragSortRecycler.setOnItemMovedListener(new DragSortRecycler.OnItemMovedListener() { + @Override + public void onItemMoved(int from, int to) { + PlaylistSong song = songs.remove(from); + songs.add(to, song); + adapter.notifyDataSetChanged(); + PlaylistsUtil.moveItem(PlaylistDetailActivity.this, playlist.id, from, to); + } + }); + + recyclerView.addItemDecoration(dragSortRecycler); + recyclerView.addOnItemTouchListener(dragSortRecycler); + recyclerView.setOnScrollListener(dragSortRecycler.getScrollListener()); } private void getIntentExtras() { @@ -51,7 +70,7 @@ public class PlaylistDetailActivity extends AbsFabActivity { private void setUpToolBar() { setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - getSupportActionBar().setTitle(playlist.playlistName); + getSupportActionBar().setTitle(playlist.name); getSupportActionBar().setDisplayHomeAsUpEnabled(true); } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivityfragments/PlaylistViewFragment.java b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivityfragments/PlaylistViewFragment.java index caf221f8..824fb203 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivityfragments/PlaylistViewFragment.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/fragments/mainactivityfragments/PlaylistViewFragment.java @@ -2,20 +2,19 @@ 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.App; 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.DataBaseChangedEvent; import com.kabouzeid.gramophone.model.Playlist; -import com.kabouzeid.gramophone.model.Song; +import com.squareup.otto.Subscribe; import java.util.List; @@ -37,12 +36,27 @@ public class PlaylistViewFragment extends AbsMainActivityFragment { } private void setUpRecyclerView() { - List 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()); + setUpAdapter(); + } + + private void setUpAdapter(){ + if(recyclerView != null) { + List playlists = PlaylistLoader.getAllPlaylists(getActivity()); + PlaylistAdapter adapter = new PlaylistAdapter(getActivity(), playlists); + recyclerView.setAdapter(adapter); + } + } + + @Subscribe + public void onDataBaseEvent(DataBaseChangedEvent event) { + switch (event.getAction()) { + case DataBaseChangedEvent.PLAYLISTS_CHANGED: + case DataBaseChangedEvent.DATABASE_CHANGED: + setUpAdapter(); + break; + } } @Override @@ -56,4 +70,16 @@ public class PlaylistViewFragment extends AbsMainActivityFragment { super.disableViews(); recyclerView.setEnabled(false); } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + App.bus.register(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + App.bus.unregister(this); + } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/PlaylistsUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/PlaylistsUtil.java index a3f538c5..54210edb 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/PlaylistsUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/PlaylistsUtil.java @@ -5,9 +5,13 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.net.Uri; +import android.provider.BaseColumns; import android.provider.MediaStore; import android.widget.Toast; +import com.kabouzeid.gramophone.App; +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.model.DataBaseChangedEvent; import com.kabouzeid.gramophone.model.PlaylistSong; import com.kabouzeid.gramophone.model.Song; @@ -18,6 +22,9 @@ import java.util.List; * Created by karim on 16.03.15. */ public class PlaylistsUtil { + public static final String MUSIC_ONLY_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"; //$NON-NLS-2$ + public static int createPlaylist(final Context context, final String name) { if (name != null && name.length() > 0) { final ContentResolver resolver = context.getContentResolver(); @@ -32,55 +39,93 @@ public class PlaylistsUtil { 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(); + if (uri != null) { + Toast.makeText(context, context.getResources().getString(R.string.created_playlist) + name, Toast.LENGTH_SHORT).show(); + App.bus.post(new DataBaseChangedEvent(DataBaseChangedEvent.PLAYLISTS_CHANGED)); + return Integer.parseInt(uri.getLastPathSegment()); + } } cursor.close(); } + Toast.makeText(context, context.getResources().getString(R.string.create_playlist_failed) + name, Toast.LENGTH_SHORT).show(); return -1; } - public static void deletePlaylist(final Context context, final int playlistId) { + public static void clearPlaylist(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) { + public static void deletePlaylist(final Context context, final int playlistId) { + final Uri uri = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI; + String where = MediaStore.Audio.Playlists._ID + "=?"; + String[] whereVal = {String.valueOf(playlistId)}; + context.getContentResolver().delete(uri, where, whereVal); + Toast.makeText(context, context.getResources().getString(R.string.deleted_playlist) + getNameForPlaylist(context, playlistId), Toast.LENGTH_SHORT).show(); + App.bus.post(new DataBaseChangedEvent(DataBaseChangedEvent.PLAYLISTS_CHANGED)); + } + + public static void addToPlaylist(final Context context, final Song song, final int playlistId) { List helperList = new ArrayList<>(); helperList.add(song); addToPlaylist(context, helperList, playlistId); } - public static void addToPlaylist(final Context context, final List songs, final long playlistId) { + public static void addToPlaylist(final Context context, final List songs, final int playlistId) { + final int size = songs.size(); final ContentResolver resolver = context.getContentResolver(); - final String[] projection = new String[]{ - MediaStore.Audio.PlaylistsColumns.NAME + "max(" + MediaStore.Audio.Playlists.Members.PLAY_ORDER + ")", }; - 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(); + Cursor cursor = null; + int base = 0; - 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); + try { + cursor = resolver.query(uri, projection, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + base = cursor.getInt(0) + 1; + } + } finally { + if (cursor != null) { + cursor.close(); + } } - resolver.bulkInsert(uri, contentValues); - Toast.makeText(context, "Added " + contentValues.length + " songs to playlist " + playlistName, Toast.LENGTH_SHORT).show(); - //TODO add string resource + int numinserted = 0; + for (int offSet = 0; offSet < size; offSet += 1000) { + numinserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base)); + } + + Toast.makeText(context, context.getResources().getString(R.string.inserted_titles_to_playlist_1) + numinserted + context.getResources().getString(R.string.inserted_titles_to_playlist_2) + getNameForPlaylist(context, playlistId) + ".", Toast.LENGTH_SHORT).show(); + App.bus.post(new DataBaseChangedEvent(DataBaseChangedEvent.PLAYLISTS_CHANGED)); + } + + public static ContentValues[] makeInsertItems(final List songs, final int offset, int len, final int base) { + if (offset + len > songs.size()) { + len = songs.size() - offset; + } + + ContentValues[] contentValues = new ContentValues[len]; + + for (int i = 0; i < len; i++) { + contentValues[i] = new ContentValues(); + contentValues[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, base + offset + i); + contentValues[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, songs.get(offset + i).id); + } + return contentValues; } 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 selection = MediaStore.Audio.Playlists.Members._ID + " =?"; String[] selectionArgs = new String[]{String.valueOf(song.idInPlayList)}; context.getContentResolver().delete(uri, selection, selectionArgs); + App.bus.post(new DataBaseChangedEvent(DataBaseChangedEvent.PLAYLISTS_CHANGED)); } public static void removeFromPlaylist(final Context context, final List songs) { @@ -95,5 +140,47 @@ public class PlaylistsUtil { selection = selection.substring(0, selection.length() - 2) + ")"; context.getContentResolver().delete(uri, selection, selectionArgs); + App.bus.post(new DataBaseChangedEvent(DataBaseChangedEvent.PLAYLISTS_CHANGED)); + } + + public static int getSongCountForPlaylist(final Context context, final long playlistId) { + Cursor c = context.getContentResolver().query( + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), + new String[]{BaseColumns._ID}, MUSIC_ONLY_SELECTION, null, null); + + if (c != null) { + int count = 0; + if (c.moveToFirst()) { + count = c.getCount(); + } + c.close(); + return count; + } + + return 0; + } + + public static void moveItem(final Context context, int playlistId, int from, int to) { + MediaStore.Audio.Playlists.Members.moveItem(context.getContentResolver(), + playlistId, from, to); + } + + public static String getNameForPlaylist(final Context context, final int id) { + Cursor cursor = context.getContentResolver().query( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + new String[]{MediaStore.Audio.PlaylistsColumns.NAME}, + BaseColumns._ID + "=?", + new String[]{String.valueOf(id)}, + null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getString(0); + } + } finally { + cursor.close(); + } + } + return ""; } } diff --git a/app/src/main/res/layout/dialog_create_playlist.xml b/app/src/main/res/layout/dialog_create_playlist.xml new file mode 100644 index 00000000..a938862c --- /dev/null +++ b/app/src/main/res/layout/dialog_create_playlist.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_grid_album.xml b/app/src/main/res/layout/item_grid_album.xml index 4be1d63c..743cd272 100644 --- a/app/src/main/res/layout/item_grid_album.xml +++ b/app/src/main/res/layout/item_grid_album.xml @@ -4,13 +4,13 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_margin="4dp" android:elevation="3dp" android:foreground="?rect_selector"> diff --git a/app/src/main/res/menu/drawer.xml b/app/src/main/res/menu/drawer.xml index 83347373..2b021c44 100644 --- a/app/src/main/res/menu/drawer.xml +++ b/app/src/main/res/menu/drawer.xml @@ -10,10 +10,10 @@ app:showAsAction="always"/> + android:id="@+id/action_search" + android:icon="@drawable/abc_ic_search_api_mtrl_alpha" + android:title="@string/action_search" + app:showAsAction="ifRoom"/> + + diff --git a/app/src/main/res/menu/global.xml b/app/src/main/res/menu/global.xml deleted file mode 100644 index e12e5d0f..00000000 --- a/app/src/main/res/menu/global.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - diff --git a/app/src/main/res/menu/menu_playlists.xml b/app/src/main/res/menu/menu_playlists.xml new file mode 100644 index 00000000..988b36a2 --- /dev/null +++ b/app/src/main/res/menu/menu_playlists.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + \ 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 ca9908da..bb9ae8d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -73,8 +73,16 @@ Loading… Added 1 title to the playing queue. Added - titles to the playing queue. + \u0020titles to the playing queue. PlaylistDetailActivity Delete from playlist + Inserted\u0020 + \u0020titles to playlist\u0020 + New playlist + Cancel + Created playlist\u0020 + Deleted playlist\u0020 + Could not create playlist\u0020 + Delete playlist\u0020