Added playing or queueing artist/album/genre from music screen

Implemented context menus in GridView items in AlbumListFragment,
ArtistListFragment and AudioGenreListFragment.
This allows the user to add everything from an artist, album, or
genre by popping up the context menu and selecting either play
or Add to playlist.

Created a new class utils/MediaManager to hold any methods related
to managing media. Added methods play(...) and queueAudio(...) to
add musiclist items to the current playlist and start playing.
This commit is contained in:
Martijn Brekhof 2015-07-28 15:26:37 +02:00
parent dab9bc055e
commit a7874de0e4
8 changed files with 318 additions and 8 deletions

View File

@ -41,6 +41,7 @@ import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
@ -49,10 +50,12 @@ import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiException;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.jsonrpc.type.PlaylistType;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.provider.MediaDatabase;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.MediaManager;
import org.xbmc.kore.utils.UIUtils;
import butterknife.ButterKnife;
@ -341,7 +344,7 @@ public class AlbumListFragment extends Fragment
final int YEAR = 6;
}
private static class AlbumsAdapter extends CursorAdapter {
private class AlbumsAdapter extends CursorAdapter {
private HostManager hostManager;
private int artWidth, artHeight;
@ -396,6 +399,11 @@ public class AlbumListFragment extends Fragment
UIUtils.loadImageWithCharacterAvatar(context, hostManager,
thumbnail, viewHolder.albumTitle,
viewHolder.artView, artWidth, artHeight);
// For the popupmenu
ImageView contextMenu = (ImageView)view.findViewById(R.id.list_context_menu);
contextMenu.setTag(viewHolder);
contextMenu.setOnClickListener(albumlistItemMenuClickListener);
}
}
@ -411,4 +419,32 @@ public class AlbumListFragment extends Fragment
int albumId;
String albumTitle;
}
private View.OnClickListener albumlistItemMenuClickListener = new View.OnClickListener() {
@Override
public void onClick(final View v) {
final ViewHolder viewHolder = (ViewHolder)v.getTag();
final PlaylistType.Item playListItem = new PlaylistType.Item();
playListItem.albumid = viewHolder.albumId;
final PopupMenu popupMenu = new PopupMenu(getActivity(), v);
popupMenu.getMenuInflater().inflate(R.menu.musiclist_item, popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_play:
MediaManager.play(AlbumListFragment.this, playListItem);
return true;
case R.id.action_queue:
MediaManager.queueAudio(AlbumListFragment.this, playListItem);
return true;
}
return false;
}
});
popupMenu.show();
}
};
}

View File

@ -41,6 +41,7 @@ import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
@ -49,10 +50,12 @@ import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiException;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.jsonrpc.type.PlaylistType;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.provider.MediaDatabase;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.MediaManager;
import org.xbmc.kore.utils.UIUtils;
import butterknife.ButterKnife;
@ -84,7 +87,6 @@ public class ArtistListFragment extends Fragment
// Activity listener
private OnArtistSelectedListener listenerActivity;
private HostManager hostManager;
private HostInfo hostInfo;
private EventBus bus;
@ -103,8 +105,7 @@ public class ArtistListFragment extends Fragment
ButterKnife.inject(this, root);
bus = EventBus.getDefault();
hostManager = HostManager.getInstance(getActivity());
hostInfo = hostManager.getHostInfo();
hostInfo = HostManager.getInstance(getActivity()).getHostInfo();
swipeRefreshLayout.setOnRefreshListener(this);
//UIUtils.setSwipeRefreshLayoutColorScheme(swipeRefreshLayout);
@ -305,7 +306,7 @@ public class ArtistListFragment extends Fragment
final int THUMBNAIL = 4;
}
private static class ArtistsAdapter extends CursorAdapter {
private class ArtistsAdapter extends CursorAdapter {
private HostManager hostManager;
private int artWidth, artHeight;
@ -354,6 +355,11 @@ public class ArtistListFragment extends Fragment
UIUtils.loadImageWithCharacterAvatar(context, hostManager,
thumbnail, viewHolder.artistName,
viewHolder.artView, artWidth, artHeight);
// For the popupmenu
ImageView contextMenu = (ImageView)view.findViewById(R.id.list_context_menu);
contextMenu.setTag(viewHolder);
contextMenu.setOnClickListener(artistlistItemMenuClickListener);
}
}
@ -368,4 +374,32 @@ public class ArtistListFragment extends Fragment
int artistId;
String artistName;
}
private View.OnClickListener artistlistItemMenuClickListener = new View.OnClickListener() {
@Override
public void onClick(final View v) {
final ViewHolder viewHolder = (ViewHolder)v.getTag();
final PlaylistType.Item playListItem = new PlaylistType.Item();
playListItem.artistid = viewHolder.artistId;
final PopupMenu popupMenu = new PopupMenu(getActivity(), v);
popupMenu.getMenuInflater().inflate(R.menu.musiclist_item, popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_play:
MediaManager.play(ArtistListFragment.this, playListItem);
return true;
case R.id.action_queue:
MediaManager.queueAudio(ArtistListFragment.this, playListItem);
return true;
}
return false;
}
});
popupMenu.show();
}
};
}

View File

@ -41,6 +41,7 @@ import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
@ -49,9 +50,11 @@ import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiException;
import org.xbmc.kore.jsonrpc.event.MediaSyncEvent;
import org.xbmc.kore.jsonrpc.type.PlaylistType;
import org.xbmc.kore.provider.MediaContract;
import org.xbmc.kore.service.LibrarySyncService;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.MediaManager;
import org.xbmc.kore.utils.UIUtils;
import butterknife.ButterKnife;
@ -291,7 +294,7 @@ public class AudioGenresListFragment extends Fragment
final int THUMBNAIL = 3;
}
private static class AudioGenresAdapter extends CursorAdapter {
private class AudioGenresAdapter extends CursorAdapter {
private HostManager hostManager;
private int artWidth, artHeight;
@ -337,6 +340,11 @@ public class AudioGenresListFragment extends Fragment
UIUtils.loadImageWithCharacterAvatar(context, hostManager,
thumbnail, viewHolder.genreTitle,
viewHolder.artView, artWidth, artHeight);
// For the popupmenu
ImageView contextMenu = (ImageView)view.findViewById(R.id.list_context_menu);
contextMenu.setTag(viewHolder);
contextMenu.setOnClickListener(genrelistItemMenuClickListener);
}
}
@ -350,4 +358,32 @@ public class AudioGenresListFragment extends Fragment
int genreId;
String genreTitle;
}
private View.OnClickListener genrelistItemMenuClickListener = new View.OnClickListener() {
@Override
public void onClick(final View v) {
final ViewHolder viewHolder = (ViewHolder)v.getTag();
final PlaylistType.Item playListItem = new PlaylistType.Item();
playListItem.genreid = viewHolder.genreId;
final PopupMenu popupMenu = new PopupMenu(getActivity(), v);
popupMenu.getMenuInflater().inflate(R.menu.musiclist_item, popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_play:
MediaManager.play(AudioGenresListFragment.this, playListItem);
return true;
case R.id.action_queue:
MediaManager.queueAudio(AudioGenresListFragment.this, playListItem);
return true;
}
return false;
}
});
popupMenu.show();
}
};
}

View File

@ -0,0 +1,142 @@
/*
* Copyright 2015 Synced Synapse. All rights reserved.
*
* 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 org.xbmc.kore.utils;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.view.View;
import android.widget.Toast;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.method.Playlist;
import org.xbmc.kore.jsonrpc.type.PlaylistType;
import org.xbmc.kore.ui.RemoteActivity;
import java.util.ArrayList;
public class MediaManager {
/**
* Clears current playlist and starts playing item
* @param fragment Fragment instance from which this method is called
* @param item PlaylistType.Item that needs to be played
*/
public static void play(final Fragment fragment, final PlaylistType.Item item) {
HostManager hostManager = HostManager.getInstance(fragment.getActivity());
final Handler callbackHandler = new Handler();
final Context context = fragment.getActivity();
Player.Open action = new Player.Open(item);
action.execute(hostManager.getConnection(), new ApiCallback<String>() {
@Override
public void onSuccess(String result) {
if (!fragment.isAdded()) return;
boolean switchToRemote = PreferenceManager
.getDefaultSharedPreferences(context)
.getBoolean(Settings.KEY_PREF_SWITCH_TO_REMOTE_AFTER_MEDIA_START,
Settings.DEFAULT_PREF_SWITCH_TO_REMOTE_AFTER_MEDIA_START);
if (switchToRemote) {
Intent launchIntent = new Intent(context, RemoteActivity.class);
context.startActivity(launchIntent);
} else {
Toast.makeText(context, R.string.now_playing, Toast.LENGTH_SHORT)
.show();
}
}
@Override
public void onError(int errorCode, String description) {
if (!fragment.isAdded()) return;
// Got an error, show toast
String errorMessage = context.getString(R.string.error_play_media_file, description);
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT)
.show();
}
}, callbackHandler);
}
/**
* Clears current playlist and starts playing item
* @param fragment Fragment instance from which this method is called
* @param item PlaylistType.Item that needs to be added to the current audio playlist
*/
public static void queueAudio(final Fragment fragment, final PlaylistType.Item item) {
Playlist.GetPlaylists getPlaylists = new Playlist.GetPlaylists();
final Handler callbackHandler = new Handler();
final Context context = fragment.getActivity();
final HostManager hostManager = HostManager.getInstance(fragment.getActivity());
getPlaylists.execute(hostManager.getConnection(), new ApiCallback<ArrayList<PlaylistType.GetPlaylistsReturnType>>() {
@Override
public void onSuccess(ArrayList<PlaylistType.GetPlaylistsReturnType> result) {
if (!fragment.isAdded()) return;
// Ok, loop through the playlists, looking for the audio one
int audioPlaylistId = -1;
for (PlaylistType.GetPlaylistsReturnType playlist : result) {
if (playlist.type.equals(PlaylistType.GetPlaylistsReturnType.AUDIO)) {
audioPlaylistId = playlist.playlistid;
break;
}
}
// If found, add to playlist
if (audioPlaylistId != -1) {
Playlist.Add action = new Playlist.Add(audioPlaylistId, item);
action.execute(hostManager.getConnection(), new ApiCallback<String>() {
@Override
public void onSuccess(String result) {
if (!fragment.isAdded()) return;
Toast.makeText(context, R.string.item_added_to_playlist, Toast.LENGTH_SHORT)
.show();
}
@Override
public void onError(int errorCode, String description) {
if (!fragment.isAdded()) return;
// Got an error, show toast
String errorMessage = context.getString(R.string.error_queue_media_file, description);
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT)
.show();
}
}, callbackHandler);
} else {
Toast.makeText(context, R.string.no_suitable_playlist, Toast.LENGTH_SHORT)
.show();
}
}
@Override
public void onError(int errorCode, String description) {
if (!fragment.isAdded()) return;
// Got an error, show toast
Toast.makeText(context, R.string.error_getting_playlist, Toast.LENGTH_SHORT)
.show();
}
}, callbackHandler);
}
}

View File

@ -58,5 +58,16 @@
android:layout_alignLeft="@id/title"
android:layout_alignParentBottom="true"
style="@style/TextAppearance.Medialist.OtherInfo"/>
<ImageView
android:id="@+id/list_context_menu"
android:layout_width="@dimen/default_icon_size"
android:layout_height="@dimen/default_icon_size"
android:padding="@dimen/default_icon_padding"
android:layout_alignTop="@id/art"
android:layout_alignParentRight="true"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconOverflow"
android:contentDescription="@string/action_options"/>
</RelativeLayout>
</android.support.v7.widget.CardView>

View File

@ -35,9 +35,10 @@
android:contentDescription="@string/poster"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_weight="1"
android:gravity="center_vertical">
<TextView
android:id="@+id/name"
@ -53,5 +54,16 @@
android:paddingBottom="0dp"/>
</LinearLayout>
<ImageView
android:id="@+id/list_context_menu"
android:layout_width="@dimen/default_icon_size"
android:layout_height="@dimen/default_icon_size"
android:padding="@dimen/default_icon_padding"
android:layout_alignTop="@id/art"
android:layout_alignParentRight="true"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconOverflow"
android:contentDescription="@string/action_options"/>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@ -34,11 +34,23 @@
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
style="@style/TextAppearance.Medialist.Title"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:gravity="center_vertical"/>
<ImageView
android:id="@+id/list_context_menu"
android:layout_width="@dimen/default_icon_size"
android:layout_height="@dimen/default_icon_size"
android:padding="@dimen/default_icon_padding"
android:layout_alignTop="@id/art"
android:layout_alignParentRight="true"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconOverflow"
android:contentDescription="@string/action_options"/>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2015 Synced Synapse. All rights reserved.
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.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_play"
android:title="@string/play"
android:orderInCategory="1"
app:showAsAction="never" />
<item android:id="@+id/action_queue"
android:title="@string/add_to_playlist"
android:orderInCategory="2"
app:showAsAction="never" />
</menu>