Implemented a slideup panel with media controls and info (#320)

* The slideup panel is only displayed when something is
         playing. It starts collapsed showing the media poster
         and title of what is currently playing.

       * Media controls implemented are volume, progress, shuffle,
         repeat and play/pause for all items. Next and previous are only
         available when a music item is playing.

       * In collapsed mode the panel will display the mute button only
         if Kodi is muted. The mute button in expanded mode is always
         visible.

       * Panel is enabled by default. Users can disable the panel
         in Settings

       * Implemented listening to Player.OnPropertyChanged notifications
         to update shuffle and repeat button states.
This commit is contained in:
Martijn Brekhof 2017-07-13 20:10:49 +02:00 committed by Synced Synapse
parent 7186874471
commit cb430aa20d
24 changed files with 1054 additions and 85 deletions

View File

@ -42,6 +42,7 @@ Credits
- [PagerSlidingTabStrip](https://github.com/astuetz/PagerSlidingTabStrip)
- [FloatingActionButton](https://github.com/makovkastar/FloatingActionButton)
- [ExpandableTextView](https://github.com/Blogcat/Android-ExpandableTextView)
- [AndroidSlidingUpPanel](https://github.com/umano/AndroidSlidingUpPanel)
Links
-----

View File

@ -125,6 +125,7 @@ dependencies {
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'at.blogc:expandabletextview:1.0.3'
compile 'com.sothree.slidinguppanel:library:3.3.1'
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'

View File

@ -77,6 +77,10 @@ public class Settings {
public static final String KEY_PREF_SHOW_NOTIFICATION = "pref_show_notification";
public static final boolean DEFAULT_PREF_SHOW_NOTIFICATION = false;
// Show now playing panel
public static final String KEY_PREF_SHOW_NOW_PLAYING_PANEL = "pref_show_nowplayingpanel";
public static final boolean DEFAULT_PREF_SHOW_NOW_PLAYING_PANEL = true;
// Pause during calls
public static final String KEY_PREF_PAUSE_DURING_CALLS = "pref_pause_during_calls";
public static final boolean DEFAULT_PREF_PAUSE_DURING_CALLS = false;

View File

@ -22,6 +22,7 @@ import org.xbmc.kore.jsonrpc.HostConnection;
import org.xbmc.kore.jsonrpc.method.JSONRPC;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.notification.Application;
import org.xbmc.kore.jsonrpc.notification.Player.NotificationsData;
import org.xbmc.kore.jsonrpc.notification.Input;
import org.xbmc.kore.jsonrpc.notification.System;
import org.xbmc.kore.jsonrpc.type.ApplicationType;
@ -73,6 +74,8 @@ public class HostConnectionObserver
PLAYER_IS_PAUSED = 3,
PLAYER_IS_STOPPED = 4;
public void playerOnPropertyChanged(NotificationsData notificationsData);
/**
* Notifies that something is playing
* @param getActivePlayerResult Active player obtained by a call to {@link org.xbmc.kore.jsonrpc.method.Player.GetActivePlayers}
@ -178,15 +181,17 @@ public class HostConnectionObserver
final int PING_AFTER_ERROR_CHECK_INTERVAL = 2000,
PING_AFTER_SUCCESS_CHECK_INTERVAL = 10000;
// If no one is listening to this, just exit
if (playerEventsObservers.isEmpty()) return;
if (playerEventsObservers.isEmpty() && applicationEventsObservers.isEmpty()) return;
JSONRPC.Ping ping = new JSONRPC.Ping();
ping.execute(connection, new ApiCallback<String>() {
@Override
public void onSuccess(String result) {
// Ok, we've got a ping, if we were in a error or uninitialized state, update
if ((hostState.lastCallResult == PlayerEventsObserver.PLAYER_NO_RESULT) ||
(hostState.lastCallResult == PlayerEventsObserver.PLAYER_CONNECTION_ERROR)) {
// Ok, we've got a ping, if there are playerEventsObservers and
// we were in a error or uninitialized state, update
if ((! playerEventsObservers.isEmpty()) &&
((hostState.lastCallResult == PlayerEventsObserver.PLAYER_NO_RESULT) ||
(hostState.lastCallResult == PlayerEventsObserver.PLAYER_CONNECTION_ERROR))) {
checkWhatsPlaying();
}
checkerHandler.postDelayed(tcpCheckerRunnable, PING_AFTER_SUCCESS_CHECK_INTERVAL);
@ -244,9 +249,7 @@ public class HostConnectionObserver
if (this.connection == null)
return;
// Save this observer and a new handle to notify him
playerEventsObservers.add(observer);
// observerHandlerMap.put(observer, new Handler());
if (replyImmediately) replyWithLastResult(observer);
@ -316,6 +319,7 @@ public class HostConnectionObserver
// as a connection observer, which we will pass to the "real" observer
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
connection.registerApplicationNotificationsObserver(this, checkerHandler);
checkerHandler.post(tcpCheckerRunnable);
} else {
checkerHandler.post(httpApplicationCheckerRunnable);
}
@ -337,7 +341,7 @@ public class HostConnectionObserver
// No more observers, so unregister us from the host connection, or stop
// the http checker thread
if (connection.getProtocol() == HostConnection.PROTOCOL_TCP) {
connection.unregisterApplicationotificationsObserver(this);
connection.unregisterApplicationNotificationsObserver(this);
} else {
checkerHandler.removeCallbacks(httpApplicationCheckerRunnable);
}
@ -364,6 +368,14 @@ public class HostConnectionObserver
hostState.lastCallResult = PlayerEventsObserver.PLAYER_NO_RESULT;
}
@Override
public void onPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.OnPropertyChanged notification) {
List<PlayerEventsObserver> allObservers = new ArrayList<>(playerEventsObservers);
for (final PlayerEventsObserver observer : allObservers) {
observer.playerOnPropertyChanged(notification.data);
}
}
/**
* The {@link HostConnection.PlayerNotificationsObserver} interface methods
*/
@ -746,7 +758,7 @@ public class HostConnectionObserver
/**
* Replies to the observer with the last result we got.
* If we have no result, nothing will be called on the observer interface.
* @param observer Obserser to call with last result
* @param observer Observer to call with last result
*/
public void replyWithLastResult(PlayerEventsObserver observer) {
switch (hostState.lastCallResult) {

View File

@ -42,7 +42,6 @@ import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.Socket;
@ -70,6 +69,7 @@ public class HostConnection {
* Interface that an observer must implement to be notified of player notifications
*/
public interface PlayerNotificationsObserver {
public void onPropertyChanged(Player.OnPropertyChanged notification);
public void onPlay(Player.OnPlay notification);
public void onPause(Player.OnPause notification);
public void onSpeedChanged(Player.OnSpeedChanged notification);
@ -274,7 +274,7 @@ public class HostConnection {
* Unregisters and observer from the input notifications
* @param observer The {@link InputNotificationsObserver}
*/
public void unregisterApplicationotificationsObserver(ApplicationNotificationsObserver observer) {
public void unregisterApplicationNotificationsObserver(ApplicationNotificationsObserver observer) {
applicationNotificationsObservers.remove(observer);
}
@ -661,6 +661,18 @@ public class HostConnection {
}
});
}
} else if (notificationName.equals(Player.OnPropertyChanged.NOTIFICATION_NAME)) {
final Player.OnPropertyChanged apiNotification = new Player.OnPropertyChanged(params);
for (final PlayerNotificationsObserver observer :
playerNotificationsObservers.keySet()) {
Handler handler = playerNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onPropertyChanged(apiNotification);
}
});
}
} else if (notificationName.equals(System.OnQuit.NOTIFICATION_NAME)) {
final System.OnQuit apiNotification = new System.OnQuit(params);
for (final SystemNotificationsObserver observer :

View File

@ -16,6 +16,7 @@
package org.xbmc.kore.jsonrpc.notification;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.BooleanNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.jsonrpc.ApiNotification;
import org.xbmc.kore.jsonrpc.type.GlobalType;
@ -26,6 +27,23 @@ import org.xbmc.kore.utils.JsonUtils;
*/
public class Player {
/**
* Player.OnPropertyChanged notification
* Player properties have changed. Such as repeat type and shuffle mode
*/
public static class OnPropertyChanged extends ApiNotification {
public static final String NOTIFICATION_NAME = "Player.OnPropertyChanged";
public final NotificationsData data;
public OnPropertyChanged(ObjectNode node) {
super(node);
data = new NotificationsData(node.get(NotificationsData.DATA_NODE));
}
public String getNotificationName() { return NOTIFICATION_NAME; }
}
/**
* Player.OnPause notification
* Playback of a media item has been paused. If there is no ID available extra information will be provided.
@ -179,15 +197,44 @@ public class Player {
}
}
/**
* Notification data for player properties
*/
public static class NotificationsProperty {
public static final String PROPERTY_NODE = "property";
public final Boolean shuffled;
public final String repeatMode;
public NotificationsProperty(JsonNode node) {
JsonNode shuffledNode = node.get("shuffled");
if (shuffledNode != null)
shuffled = shuffledNode.asBoolean();
else
shuffled = null;
repeatMode = JsonUtils.stringFromJsonNode(node, "repeat");
}
}
public static class NotificationsData {
public static final String DATA_NODE = "data";
public final NotificationsPlayer player;
public final NotificationsItem item;
public final NotificationsProperty property;
public NotificationsData(JsonNode node) {
item = new NotificationsItem((ObjectNode)node.get(NotificationsItem.ITEM_NODE));
player = new NotificationsPlayer((ObjectNode)node.get(NotificationsPlayer.PLAYER_NODE));
JsonNode jsonNode = node.get(NotificationsItem.ITEM_NODE);
item = (jsonNode != null) ? new NotificationsItem(jsonNode) : null;
jsonNode = node.get(NotificationsPlayer.PLAYER_NODE);
player = (jsonNode != null)
? new NotificationsPlayer(jsonNode)
: null;
jsonNode = node.get(NotificationsProperty.PROPERTY_NODE);
property = (jsonNode != null) ? new NotificationsProperty(jsonNode) : null;
}
}

View File

@ -26,6 +26,7 @@ import android.support.v4.content.ContextCompat;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostConnectionObserver;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.notification.Player;
import org.xbmc.kore.jsonrpc.type.ListType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.utils.LogUtils;
@ -150,6 +151,11 @@ public class ConnectionObserversManagerService extends Service
}
}
@Override
public void playerOnPropertyChanged(Player.NotificationsData notificationsData) {
}
/**
* HostConnectionObserver.PlayerEventsObserver interface callbacks
*/

View File

@ -17,10 +17,8 @@ package org.xbmc.kore.service;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
@ -36,6 +34,7 @@ import com.squareup.picasso.Target;
import org.xbmc.kore.R;
import org.xbmc.kore.host.HostConnectionObserver;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.notification.Player;
import org.xbmc.kore.jsonrpc.type.ListType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.ui.sections.remote.RemoteActivity;
@ -68,6 +67,11 @@ public class NotificationObserver
mRemoteStartPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
}
@Override
public void playerOnPropertyChanged(Player.NotificationsData notificationsData) {
}
/**
* HostConnectionObserver.PlayerEventsObserver interface callbacks
*/

View File

@ -74,6 +74,11 @@ public class PauseCallObserver extends PhoneStateListener
}
}
@Override
public void playerOnPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.NotificationsData notificationsData) {
}
@Override
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,

View File

@ -17,43 +17,88 @@ package org.xbmc.kore.ui;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.transition.TransitionInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.ImageView;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import org.xbmc.kore.R;
import org.xbmc.kore.Settings;
import org.xbmc.kore.host.HostConnectionObserver;
import org.xbmc.kore.host.HostManager;
import org.xbmc.kore.jsonrpc.ApiCallback;
import org.xbmc.kore.jsonrpc.ApiMethod;
import org.xbmc.kore.jsonrpc.method.Application;
import org.xbmc.kore.jsonrpc.method.Player;
import org.xbmc.kore.jsonrpc.type.ListType;
import org.xbmc.kore.jsonrpc.type.PlayerType;
import org.xbmc.kore.ui.generic.NavigationDrawerFragment;
import org.xbmc.kore.ui.sections.remote.RemoteActivity;
import org.xbmc.kore.ui.widgets.MediaProgressIndicator;
import org.xbmc.kore.ui.widgets.NowPlayingPanel;
import org.xbmc.kore.ui.widgets.VolumeLevelIndicator;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.SharedElementTransition;
import org.xbmc.kore.utils.UIUtils;
import org.xbmc.kore.utils.Utils;
public abstract class BaseMediaActivity extends AppCompatActivity {
import butterknife.ButterKnife;
import butterknife.InjectView;
public abstract class BaseMediaActivity extends BaseActivity
implements HostConnectionObserver.ApplicationEventsObserver,
HostConnectionObserver.PlayerEventsObserver,
NowPlayingPanel.OnPanelButtonsClickListener,
MediaProgressIndicator.OnProgressChangeListener {
private static final String TAG = LogUtils.makeLogTag(BaseMediaActivity.class);
private static final String NAVICON_ISARROW = "navstate";
private static final String ACTIONBAR_TITLE = "actionbartitle";
@InjectView(R.id.now_playing_panel) NowPlayingPanel nowPlayingPanel;
private NavigationDrawerFragment navigationDrawerFragment;
private SharedElementTransition sharedElementTransition = new SharedElementTransition();
private boolean drawerIndicatorIsArrow;
private int currentActivePlayerId = -1;
private HostManager hostManager;
private HostConnectionObserver hostConnectionObserver;
private boolean showNowPlayingPanel;
protected abstract String getActionBarTitle();
protected abstract Fragment createFragment();
/**
* Default callback for methods that don't return anything
*/
private ApiCallback<String> defaultStringActionCallback = ApiMethod.getDefaultActionCallback();
private Handler callbackHandler = new Handler();
private ApiCallback<Integer> defaultIntActionCallback = ApiMethod.getDefaultActionCallback();
private Runnable hidePanelRunnable = new Runnable() {
@Override
public void run() {
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
};
@Override
@TargetApi(21)
protected void onCreate(Bundle savedInstanceState) {
@ -61,13 +106,10 @@ public abstract class BaseMediaActivity extends AppCompatActivity {
if (Utils.isLollipopOrLater()) {
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
setTheme(UIUtils.getThemeResourceId(
prefs.getString(Settings.KEY_PREF_THEME, Settings.DEFAULT_PREF_THEME)));
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_generic_media);
ButterKnife.inject(this);
// Set up the drawer.
navigationDrawerFragment = (NavigationDrawerFragment)getSupportFragmentManager()
@ -113,6 +155,8 @@ public abstract class BaseMediaActivity extends AppCompatActivity {
if (Utils.isLollipopOrLater()) {
sharedElementTransition.setupExitTransition(this, fragment);
}
hostManager = HostManager.getInstance(this);
}
@Override
@ -129,6 +173,38 @@ public abstract class BaseMediaActivity extends AppCompatActivity {
}
}
@Override
protected void onResume() {
super.onResume();
showNowPlayingPanel = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(Settings.KEY_PREF_SHOW_NOW_PLAYING_PANEL,
Settings.DEFAULT_PREF_SHOW_NOW_PLAYING_PANEL);
if(showNowPlayingPanel) {
setupNowPlayingPanel();
} else {
//Hide it in case we were displaying the panel and user disabled showing
//the panel in Settings
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
}
@Override
public void onPause() {
super.onPause();
if(!showNowPlayingPanel)
return;
hostConnectionObserver = hostManager.getHostConnectionObserver();
if (hostConnectionObserver == null)
return;
hostConnectionObserver.unregisterApplicationObserver(this);
hostConnectionObserver.unregisterPlayerObserver(this);
}
public boolean getDrawerIndicatorIsArrow() {
return drawerIndicatorIsArrow;
}
@ -187,4 +263,272 @@ public abstract class BaseMediaActivity extends AppCompatActivity {
.addToBackStack(null)
.commit();
}
@Override
public void applicationOnVolumeChanged(int volume, boolean muted) {
nowPlayingPanel.setVolume(volume, muted);
}
@Override
public void playerOnPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.NotificationsData notificationsData) {
if (notificationsData.property.shuffled != null)
nowPlayingPanel.setShuffled(notificationsData.property.shuffled);
if (notificationsData.property.repeatMode != null )
nowPlayingPanel.setRepeatMode(notificationsData.property.repeatMode);
}
@Override
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
currentActivePlayerId = getActivePlayerResult.playerid;
updateNowPlayingPanel(getPropertiesResult, getItemResult);
}
@Override
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult, PlayerType.PropertyValue getPropertiesResult, ListType.ItemsAll getItemResult) {
currentActivePlayerId = getActivePlayerResult.playerid;
updateNowPlayingPanel(getPropertiesResult, getItemResult);
}
@Override
public void playerOnStop() {
//We delay hiding the panel to prevent hiding the panel when playing
// the next item in a playlist
callbackHandler.removeCallbacks(hidePanelRunnable);
callbackHandler.postDelayed(hidePanelRunnable, 1000);
}
@Override
public void playerOnConnectionError(int errorCode, String description) {
}
@Override
public void playerNoResultsYet() {
}
@Override
public void observerOnStopObserving() {
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
@Override
public void systemOnQuit() {
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.HIDDEN);
}
@Override
public void inputOnInputRequested(String title, String type, String value) {
}
@Override
public void onProgressChanged(int progress) {
PlayerType.PositionTime positionTime = new PlayerType.PositionTime(progress);
Player.Seek seekAction = new Player.Seek(currentActivePlayerId, positionTime);
seekAction.execute(HostManager.getInstance(this).getConnection(), new ApiCallback<PlayerType.SeekReturnType>() {
@Override
public void onSuccess(PlayerType.SeekReturnType result) {
// Ignore
}
@Override
public void onError(int errorCode, String description) {
LogUtils.LOGE(TAG, "Got an error calling Player.Seek. Error code: " + errorCode + ", description: " + description);
}
}, new Handler());
}
@Override
public void onPlayClicked() {
Player.PlayPause action = new Player.PlayPause(currentActivePlayerId);
action.execute(hostManager.getConnection(), defaultIntActionCallback, callbackHandler);
}
@Override
public void onPreviousClicked() {
Player.GoTo action = new Player.GoTo(currentActivePlayerId, Player.GoTo.PREVIOUS);
action.execute(hostManager.getConnection(), defaultStringActionCallback, callbackHandler);
}
@Override
public void onNextClicked() {
Player.GoTo action = new Player.GoTo(currentActivePlayerId, Player.GoTo.NEXT);
action.execute(hostManager.getConnection(), defaultStringActionCallback, callbackHandler);
}
@Override
public void onVolumeMuteClicked() {
Application.SetMute action = new Application.SetMute();
action.execute(hostManager.getConnection(), new ApiCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
//We depend on the listener to correct the mute button state
}
@Override
public void onError(int errorCode, String description) { }
}, new Handler());
}
@Override
public void onShuffleClicked() {
Player.SetShuffle action = new Player.SetShuffle(currentActivePlayerId);
action.execute(hostManager.getConnection(), new ApiCallback<String>() {
@Override
public void onSuccess(String result) {
//We depend on the listener to correct the mute button state
}
@Override
public void onError(int errorCode, String description) { }
}, callbackHandler);
}
@Override
public void onRepeatClicked() {
Player.SetRepeat action = new Player.SetRepeat(currentActivePlayerId, PlayerType.Repeat.CYCLE);
action.execute(hostManager.getConnection(), new ApiCallback<String>() {
@Override
public void onSuccess(String result) {
//We depend on the listener to correct the mute button state
}
@Override
public void onError(int errorCode, String description) { }
}, callbackHandler);
}
@Override
public void onVolumeMutedIndicatorClicked() {
Application.SetMute action = new Application.SetMute();
action.execute(hostManager.getConnection(), new ApiCallback<Boolean>() {
@Override
public void onSuccess(Boolean result) {
//We depend on the listener to correct the mute button state
}
@Override
public void onError(int errorCode, String description) { }
}, new Handler());
}
private void setupNowPlayingPanel() {
nowPlayingPanel.setOnVolumeChangeListener(new VolumeLevelIndicator.OnVolumeChangeListener() {
@Override
public void onVolumeChanged(int volume) {
new Application.SetVolume(volume)
.execute(hostManager.getConnection(), defaultIntActionCallback, new Handler());
}
});
nowPlayingPanel.setOnPanelButtonsClickListener(this);
nowPlayingPanel.setOnProgressChangeListener(this);
hostConnectionObserver = hostManager.getHostConnectionObserver();
if (hostConnectionObserver == null)
return;
hostConnectionObserver.registerApplicationObserver(this, true);
hostConnectionObserver.registerPlayerObserver(this, true);
hostConnectionObserver.forceRefreshResults();
}
private void updateNowPlayingPanel(PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
String title;
String poster;
String details = null;
callbackHandler.removeCallbacks(hidePanelRunnable);
// Only set state to collapsed if panel is currently hidden. This prevents collapsing
// the panel when the user expanded the panel and started playing the item from a paused
// state
if (nowPlayingPanel.getPanelState() == SlidingUpPanelLayout.PanelState.HIDDEN) {
nowPlayingPanel.setPanelState(SlidingUpPanelLayout.PanelState.COLLAPSED);
}
nowPlayingPanel.setMediaProgress(getPropertiesResult.time, getPropertiesResult.totaltime);
nowPlayingPanel.setPlayButton(getPropertiesResult.speed > 0);
nowPlayingPanel.setShuffled(getPropertiesResult.shuffled);
nowPlayingPanel.setRepeatMode(getPropertiesResult.repeat);
nowPlayingPanel.setSpeed(getPropertiesResult.speed);
switch (getItemResult.type) {
case ListType.ItemsAll.TYPE_MOVIE:
title = getItemResult.title;
details = getItemResult.tagline;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
case ListType.ItemsAll.TYPE_EPISODE:
title = getItemResult.title;
String seasonEpisode = String.format(getString(R.string.season_episode),
getItemResult.season, getItemResult.episode);
details = String.format("%s | %s", getItemResult.showtitle, seasonEpisode);
poster = TextUtils.isEmpty(getItemResult.art.poster) ? getItemResult.art.fanart
: getItemResult.art.poster;
break;
case ListType.ItemsAll.TYPE_SONG:
title = getItemResult.title;
details = getItemResult.displayartist + " | " + getItemResult.album;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
case ListType.ItemsAll.TYPE_MUSIC_VIDEO:
title = getItemResult.title;
details = Utils.listStringConcat(getItemResult.artist, ", ") + " | " + getItemResult.album;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
case ListType.ItemsAll.TYPE_CHANNEL:
title = getItemResult.label;
details = getItemResult.title;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
default:
title = getItemResult.label;
poster = TextUtils.isEmpty(getItemResult.thumbnail) ? getItemResult.fanart
: getItemResult.thumbnail;
break;
}
if (title.contentEquals(nowPlayingPanel.getTitle()))
return; // Still showing same item as previous call
nowPlayingPanel.setTitle(title);
if (details != null) {
nowPlayingPanel.setDetails(details);
}
if ((getItemResult.type.contentEquals(ListType.ItemsAll.TYPE_MUSIC_VIDEO)) ||
(getItemResult.type.contentEquals(ListType.ItemsAll.TYPE_SONG))) {
nowPlayingPanel.setNextPrevVisibility(View.VISIBLE);
} else {
nowPlayingPanel.setNextPrevVisibility(View.GONE);
}
Resources resources = getResources();
int posterWidth = resources.getDimensionPixelOffset(R.dimen.notification_art_slim_width);
int posterHeight = resources.getDimensionPixelOffset(R.dimen.notification_art_slim_height);
// If not video, change aspect ration of poster to a square
boolean isVideo = (getItemResult.type.equals(ListType.ItemsAll.TYPE_MOVIE)) ||
(getItemResult.type.equals(ListType.ItemsAll.TYPE_EPISODE));
nowPlayingPanel.setSquarePoster(!isVideo);
UIUtils.loadImageWithCharacterAvatar(this, hostManager, poster, title,
nowPlayingPanel.getPoster(),
(isVideo) ? posterWidth : posterHeight, posterHeight);
}
}

View File

@ -84,7 +84,6 @@ public class NowPlayingFragment extends Fragment
*/
public interface NowPlayingListener {
public void SwitchToRemotePanel();
public void onShuffleClicked();
}
/**
@ -126,19 +125,17 @@ public class NowPlayingFragment extends Fragment
private int currentSubtitleIndex = -1;
private int currentAudiostreamIndex = -1;
private ApiCallback<Integer> defaultIntActionCallback = ApiMethod.getDefaultActionCallback();
private ApiCallback<Boolean> defaultBooleanActionCallback = ApiMethod.getDefaultActionCallback();
/**
* Injectable views
*/
@InjectView(R.id.play) ImageButton playButton;
@InjectView(R.id.stop) ImageButton stopButton;
@InjectView(R.id.previous) ImageButton previousButton;
@InjectView(R.id.next) ImageButton nextButton;
@InjectView(R.id.rewind) ImageButton rewindButton;
@InjectView(R.id.fast_forward) ImageButton fastForwardButton;
@InjectView(R.id.repeat) RepeatModeButton repeatButton;
@InjectView(R.id.volume_mute) HighlightButton volumeMuteButton;
@InjectView(R.id.shuffle) HighlightButton shuffleButton;
@InjectView(R.id.repeat) RepeatModeButton repeatButton;
@InjectView(R.id.overflow) ImageButton overflowButton;
@InjectView(R.id.info_panel) RelativeLayout infoPanel;
@ -156,7 +153,6 @@ public class NowPlayingFragment extends Fragment
@InjectView(R.id.volume_level_indicator) VolumeLevelIndicator volumeLevelIndicator;
@InjectView(R.id.media_details) RelativeLayout mediaDetailsPanel;
@InjectView(R.id.rating) TextView mediaRating;
@InjectView(R.id.max_rating) TextView mediaMaxRating;
@InjectView(R.id.year) TextView mediaYear;
@ -189,6 +185,16 @@ public class NowPlayingFragment extends Fragment
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment_now_playing, container, false);
ButterKnife.inject(this, root);
volumeLevelIndicator.setOnVolumeChangeListener(new VolumeLevelIndicator.OnVolumeChangeListener() {
@Override
public void onVolumeChanged(int volume) {
new Application.SetVolume(volume)
.execute(hostManager.getConnection(), defaultIntActionCallback, new Handler());
}
});
mediaProgressIndicator.setOnProgressChangeListener(this);
// Setup dim the fanart when scroll changes
// Full dim on 4 * iconSize dp
Resources resources = getActivity().getResources();
@ -242,8 +248,6 @@ public class NowPlayingFragment extends Fragment
* Default callback for methods that don't return anything
*/
private ApiCallback<String> defaultStringActionCallback = ApiMethod.getDefaultActionCallback();
private ApiCallback<Integer> defaultIntActionCallback = ApiMethod.getDefaultActionCallback();
private ApiCallback<Boolean> defaultBooleanActionCallback = ApiMethod.getDefaultActionCallback();
/**
* Callback for methods that change the play speed
@ -252,7 +256,7 @@ public class NowPlayingFragment extends Fragment
@Override
public void onSuccess(Integer result) {
if (!isAdded()) return;
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, result);
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, result == 1);
}
@Override
@ -272,7 +276,7 @@ public class NowPlayingFragment extends Fragment
public void onStopClicked(View v) {
Player.Stop action = new Player.Stop(currentActivePlayerId);
action.execute(hostManager.getConnection(), defaultStringActionCallback, callbackHandler);
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, 0);
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, false);
}
@OnClick(R.id.fast_forward)
@ -299,17 +303,8 @@ public class NowPlayingFragment extends Fragment
action.execute(hostManager.getConnection(), defaultStringActionCallback, callbackHandler);
}
/**
* Calllbacks for media button toolbar
*/
@OnClick(R.id.volume_mute)
public void onVolumeMuteClicked(View v) {
// We boldly set the mute button to the desired state before actually setting
// the mute state on the host. We do this to make it clear to the user that the button
// was pressed.
HostConnectionObserver.HostState hostState = hostConnectionObserver.getHostState();
UIUtils.highlightImageView(getActivity(), volumeMuteButton, !hostState.isVolumeMuted());
Application.SetMute action = new Application.SetMute();
action.execute(hostManager.getConnection(), defaultBooleanActionCallback, new Handler());
}
@ -323,7 +318,6 @@ public class NowPlayingFragment extends Fragment
if (!isAdded()) return;
// Force a refresh
hostConnectionObserver.forceRefreshResults();
nowPlayingListener.onShuffleClicked();
}
@Override
@ -539,20 +533,12 @@ public class NowPlayingFragment extends Fragment
}
@Override
public void onProgressChanged(int progress) {
PlayerType.PositionTime positionTime = new PlayerType.PositionTime(progress);
Player.Seek seekAction = new Player.Seek(currentActivePlayerId, positionTime);
seekAction.execute(hostManager.getConnection(), new ApiCallback<PlayerType.SeekReturnType>() {
@Override
public void onSuccess(PlayerType.SeekReturnType result) {
// Ignore
}
public void playerOnPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.NotificationsData notificationsData) {
if (notificationsData.property.shuffled != null)
shuffleButton.setHighlight(notificationsData.property.shuffled);
@Override
public void onError(int errorCode, String description) {
LogUtils.LOGD(TAG, "Got an error calling Player.Seek. Error code: " + errorCode + ", description: " + description);
}
}, callbackHandler);
if (notificationsData.property.repeatMode != null)
UIUtils.setRepeatButton(repeatButton, notificationsData.property.repeatMode);
}
/**
@ -561,19 +547,19 @@ public class NowPlayingFragment extends Fragment
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
setNowPlayingInfo(getPropertiesResult, getItemResult);
setNowPlayingInfo(getActivePlayerResult, getPropertiesResult, getItemResult);
currentActivePlayerId = getActivePlayerResult.playerid;
// Switch icon
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, getPropertiesResult.speed);
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, getPropertiesResult.speed == 1);
}
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
setNowPlayingInfo(getPropertiesResult, getItemResult);
setNowPlayingInfo(getActivePlayerResult, getPropertiesResult, getItemResult);
currentActivePlayerId = getActivePlayerResult.playerid;
// Switch icon
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, getPropertiesResult.speed);
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, getPropertiesResult.speed == 1);
}
public void playerOnStop() {
@ -619,18 +605,38 @@ public class NowPlayingFragment extends Fragment
@Override
public void applicationOnVolumeChanged(int volume, boolean muted) {
volumeLevelIndicator.setVolume(muted, volume);
UIUtils.highlightImageView(getActivity(), volumeMuteButton, muted);
volumeMuteButton.setHighlight(muted);
}
// Ignore this
public void inputOnInputRequested(String title, String type, String value) {}
public void observerOnStopObserving() {}
@Override
public void onProgressChanged(int progress) {
PlayerType.PositionTime positionTime = new PlayerType.PositionTime(progress);
Player.Seek seekAction = new Player.Seek(currentActivePlayerId, positionTime);
seekAction.execute(HostManager.getInstance(getContext()).getConnection(), new ApiCallback<PlayerType.SeekReturnType>() {
@Override
public void onSuccess(PlayerType.SeekReturnType result) {
// Ignore
}
@Override
public void onError(int errorCode, String description) {
LogUtils.LOGD("MediaSeekBar", "Got an error calling Player.Seek. Error code: " + errorCode + ", description: " + description);
}
}, new Handler());
}
/**
* Sets whats playing information
* @param getItemResult Return from method {@link org.xbmc.kore.jsonrpc.method.Player.GetItem}
*/
private void setNowPlayingInfo(PlayerType.PropertyValue getPropertiesResult,
private void setNowPlayingInfo(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
final ListType.ItemsAll getItemResult) {
final String title, underTitle, art, poster, genreSeason, year,
descriptionPlot, votes, maxRating;
@ -776,7 +782,6 @@ public class NowPlayingFragment extends Fragment
}
UIUtils.setRepeatButton(repeatButton, getPropertiesResult.repeat);
shuffleButton.setHighlight(getPropertiesResult.shuffled);
Resources resources = getActivity().getResources();

View File

@ -194,6 +194,12 @@ public class PlaylistFragment extends Fragment
private PlayerType.PropertyValue lastGetPropertiesResult;
private List<ListType.ItemsAll> lastGetPlaylistItemsResult = null;
@Override
public void playerOnPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.NotificationsData notificationsData) {
if (notificationsData.property.shuffled != null)
setupPlaylistInfo(lastGetActivePlayerResult, lastGetPropertiesResult, lastGetItemResult);
}
/**
* HostConnectionObserver.PlayerEventsObserver interface callbacks
*/

View File

@ -610,6 +610,12 @@ public class RemoteActivity extends BaseActivity
* HostConnectionObserver.PlayerEventsObserver interface callbacks
*/
private String lastImageUrl = null;
@Override
public void playerOnPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.NotificationsData notificationsData) {
}
public void playerOnPlay(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
PlayerType.PropertyValue getPropertiesResult,
ListType.ItemsAll getItemResult) {
@ -690,11 +696,6 @@ public class RemoteActivity extends BaseActivity
viewPager.setCurrentItem(1);
}
@Override
public void onShuffleClicked() {
refreshPlaylist();
}
private void refreshPlaylist() {
String tag = "android:switcher:" + viewPager.getId() + ":" + PLAYLIST_FRAGMENT_ID;
PlaylistFragment playlistFragment = (PlaylistFragment)getSupportFragmentManager()

View File

@ -552,13 +552,18 @@ public class RemoteFragment extends Fragment
@Override
public void onSuccess(Integer result) {
if (!isAdded()) return;
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, result);
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, result == 1);
}
@Override
public void onError(int errorCode, String description) { }
};
@Override
public void playerOnPropertyChanged(org.xbmc.kore.jsonrpc.notification.Player.NotificationsData notificationsData) {
}
/**
* HostConnectionObserver.PlayerEventsObserver interface callbacks
*/
@ -569,7 +574,7 @@ public class RemoteFragment extends Fragment
currentActivePlayerId = getActivePlayerResult.playerid;
currentNowPlayingItemType = getItemResult.type;
// Switch icon
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, getPropertiesResult.speed);
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, getPropertiesResult.speed == 1);
}
public void playerOnPause(PlayerType.GetActivePlayersReturnType getActivePlayerResult,
@ -579,7 +584,7 @@ public class RemoteFragment extends Fragment
currentActivePlayerId = getActivePlayerResult.playerid;
currentNowPlayingItemType = getItemResult.type;
// Switch icon
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, getPropertiesResult.speed);
UIUtils.setPlayPauseButtonIcon(getActivity(), playButton, getPropertiesResult.speed == 1);
}
public void playerOnStop() {

View File

@ -18,6 +18,8 @@ package org.xbmc.kore.ui.widgets;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
@ -25,6 +27,7 @@ import android.widget.SeekBar;
import android.widget.TextView;
import org.xbmc.kore.R;
import org.xbmc.kore.utils.LogUtils;
import org.xbmc.kore.utils.UIUtils;
public class MediaProgressIndicator extends LinearLayout {
@ -94,6 +97,24 @@ public class MediaProgressIndicator extends LinearLayout {
});
}
@Override
protected Parcelable onSaveInstanceState() {
SavedState savedState = new SavedState(super.onSaveInstanceState());
savedState.progress = progress;
savedState.maxProgress = maxProgress;
return savedState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
progress = savedState.progress;
maxProgress = savedState.maxProgress;
setProgress(progress);
setMaxProgress(maxProgress);
}
private Runnable seekBarUpdater = new Runnable() {
@Override
public void run() {
@ -119,6 +140,10 @@ public class MediaProgressIndicator extends LinearLayout {
progressTextView.setText(UIUtils.formatTime(progress));
}
public int getProgress() {
return progress;
}
public void setMaxProgress(int max) {
maxProgress = max;
seekBar.setMax(max);
@ -140,4 +165,37 @@ public class MediaProgressIndicator extends LinearLayout {
if (speed > 0)
seekBar.postDelayed(seekBarUpdater, SEEK_BAR_UPDATE_INTERVAL);
}
private static class SavedState extends BaseSavedState {
int progress;
int maxProgress;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
progress = in.readInt();
maxProgress = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(progress);
out.writeInt(maxProgress);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

View File

@ -0,0 +1,271 @@
/*
* Copyright 2017 Martijn Brekhof. 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.ui.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import com.sothree.slidinguppanel.SlidingUpPanelLayout;
import org.xbmc.kore.R;
import org.xbmc.kore.jsonrpc.type.GlobalType;
import org.xbmc.kore.utils.UIUtils;
public class NowPlayingPanel extends SlidingUpPanelLayout {
public interface OnPanelButtonsClickListener {
void onPlayClicked();
void onPreviousClicked();
void onNextClicked();
void onVolumeMuteClicked();
void onShuffleClicked();
void onRepeatClicked();
void onVolumeMutedIndicatorClicked();
}
private OnPanelButtonsClickListener onPanelButtonsClickListener;
private TextView title;
private TextView details;
private ImageView poster;
private ImageButton previousButton;
private ImageButton nextButton;
private ImageButton playButton;
private MediaProgressIndicator mediaProgressIndicator;
private VolumeLevelIndicator volumeLevelIndicator;
private HighlightButton volumeMuteButton;
private HighlightButton volumeMutedIndicatorButton;
private RepeatModeButton repeatModeButton;
private HighlightButton shuffleButton;
public NowPlayingPanel(Context context) {
super(context);
initializeView(context);
}
public NowPlayingPanel(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
initializeView(context);
}
public NowPlayingPanel(Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
initializeView(context);
}
private void initializeView(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.now_playing_panel, this);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
title = (TextView) findViewById(R.id.npp_title);
details = (TextView) findViewById(R.id.npp_details);
poster = (ImageView) findViewById(R.id.npp_poster);
previousButton = (ImageButton) findViewById(R.id.npp_previous);
nextButton = (ImageButton) findViewById(R.id.npp_next);
playButton = (ImageButton) findViewById(R.id.npp_play);
mediaProgressIndicator = (MediaProgressIndicator) findViewById(R.id.npp_progress_indicator);
volumeLevelIndicator = (VolumeLevelIndicator) findViewById(R.id.npp_volume_level_indicator);
volumeMuteButton = (HighlightButton) findViewById(R.id.npp_volume_mute);
repeatModeButton = (RepeatModeButton) findViewById(R.id.npp_repeat);
shuffleButton = (HighlightButton) findViewById(R.id.npp_shuffle);
volumeMutedIndicatorButton = (HighlightButton) findViewById(R.id.npp_volume_muted_indicator);
setupButtonClickListeners();
}
public void setOnPanelButtonsClickListener(OnPanelButtonsClickListener listener) {
onPanelButtonsClickListener = listener;
}
public void setOnVolumeChangeListener(VolumeLevelIndicator.OnVolumeChangeListener listener) {
volumeLevelIndicator.setOnVolumeChangeListener(listener);
}
public void setOnProgressChangeListener(MediaProgressIndicator.OnProgressChangeListener listener) {
mediaProgressIndicator.setOnProgressChangeListener(listener);
}
public void setVolume(int volume, boolean muted) {
volumeLevelIndicator.setVolume(muted, volume);
if (muted) {
volumeMutedIndicatorButton.setVisibility(View.VISIBLE);
} else {
volumeMutedIndicatorButton.setVisibility(View.GONE);
}
volumeMutedIndicatorButton.setHighlight(muted);
volumeMuteButton.setHighlight(muted);
}
public void setRepeatMode(String repeatMode) {
UIUtils.setRepeatButton(repeatModeButton, repeatMode);
}
public void setShuffled(boolean shuffled) {
shuffleButton.setHighlight(shuffled);
}
/**
* Sets the state of the play button
* @param play true if playing, false if paused
*/
public void setPlayButton(boolean play) {
UIUtils.setPlayPauseButtonIcon(getContext(), playButton, play);
}
public void setMediaProgress(GlobalType.Time time, GlobalType.Time totalTime) {
mediaProgressIndicator.setMaxProgress(totalTime.ToSeconds());
mediaProgressIndicator.setProgress(time.ToSeconds());
}
/**
* Returns the progression indicator used for media progression
* @return
*/
public MediaProgressIndicator getMediaProgress() {
return mediaProgressIndicator;
}
/**
*
* @param speed
*/
public void setSpeed(int speed) {
mediaProgressIndicator.setSpeed(speed);
}
public CharSequence getTitle() {
return title.getText();
}
public void setTitle(String title) {
this.title.setText(title);
}
public void setDetails(String details) {
this.details.setText(details);
}
public void setNextPrevVisibility(int visibility) {
nextButton.setVisibility(visibility);
previousButton.setVisibility(visibility);
}
public void setSquarePoster(boolean square) {
if (square) {
ViewGroup.LayoutParams layoutParams = poster.getLayoutParams();
layoutParams.width = layoutParams.height;
poster.setLayoutParams(layoutParams);
}
}
public ImageView getPoster() {
return poster;
}
private void setupButtonClickListeners() {
playButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleButtonClickEvent(v);
}
});
previousButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleButtonClickEvent(v);
}
});
nextButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleButtonClickEvent(v);
}
});
volumeMuteButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleButtonClickEvent(v);
}
});
shuffleButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleButtonClickEvent(v);
}
});
repeatModeButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleButtonClickEvent(v);
}
});
volumeMutedIndicatorButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handleButtonClickEvent(v);
}
});
}
private void handleButtonClickEvent(View view) {
if (onPanelButtonsClickListener == null)
return;
switch (view.getId()) {
case R.id.npp_previous:
onPanelButtonsClickListener.onPreviousClicked();
break;
case R.id.npp_next:
onPanelButtonsClickListener.onNextClicked();
break;
case R.id.npp_play:
onPanelButtonsClickListener.onPlayClicked();
break;
case R.id.npp_volume_mute:
onPanelButtonsClickListener.onVolumeMuteClicked();
break;
case R.id.npp_repeat:
onPanelButtonsClickListener.onRepeatClicked();
break;
case R.id.npp_shuffle:
onPanelButtonsClickListener.onShuffleClicked();
break;
case R.id.npp_volume_muted_indicator:
onPanelButtonsClickListener.onVolumeMutedIndicatorClicked();
break;
}
}
}

View File

@ -33,8 +33,8 @@ public class LogUtils {
// TODO: Remove this later
private static final List<String> doNotLogTags = Arrays.asList(
HostConnection.TAG,
HostConnectionObserver.TAG
// HostConnection.TAG,
// HostConnectionObserver.TAG
);
public static String makeLogTag(String str) {

View File

@ -93,6 +93,21 @@ public class UIUtils {
}
}
/**
* Converts the time format from {@link #formatTime(int, int, int)} to seconds
* @param time
* @return
*/
public static int timeToSeconds(String time) {
String[] items = time.split(":");
if (items.length > 2) {
return (Integer.parseInt(items[0]) * 3600) + (Integer.parseInt(items[1]) * 60) +
(Integer.parseInt(items[2]));
} else {
return (Integer.parseInt(items[0]) * 60) + (Integer.parseInt(items[1]));
}
}
/**
* Formats a file size, ISO prefixes
*/
@ -198,9 +213,9 @@ public class UIUtils {
char charAvatar = TextUtils.isEmpty(str) ?
' ' : str.charAt(0);
int avatarColorsIdx = TextUtils.isEmpty(str) ? 0 :
Math.max(Character.getNumericValue(str.charAt(0)) +
Character.getNumericValue(str.charAt(str.length() - 1)) +
str.length(), 0) % characterAvatarColors.length();
Math.max(Character.getNumericValue(str.charAt(0)) +
Character.getNumericValue(str.charAt(str.length() - 1)) +
str.length(), 0) % characterAvatarColors.length();
int color = characterAvatarColors.getColor(avatarColorsIdx, 0xff000000);
// avatarColorsIdx = randomGenerator.nextInt(characterAvatarColors.length());
return new CharacterDrawable(charAvatar, color);
@ -210,12 +225,12 @@ public class UIUtils {
static int iconPauseResId = R.drawable.ic_pause_white_24dp,
iconPlayResId = R.drawable.ic_play_arrow_white_24dp;
/**
* Sets play/pause button icon on a ImageView, based on speed
* Sets play/pause button icon on a ImageView
* @param context Activity
* @param view ImageView/ImageButton
* @param speed Current player speed
* @param play true if playing, false if paused
*/
public static void setPlayPauseButtonIcon(Context context, ImageView view, int speed) {
public static void setPlayPauseButtonIcon(Context context, ImageView view, boolean play) {
if (!playPauseIconsLoaded) {
TypedArray styledAttributes = context.obtainStyledAttributes(new int[]{R.attr.iconPause, R.attr.iconPlay});
@ -225,7 +240,7 @@ public class UIUtils {
playPauseIconsLoaded = true;
}
view.setImageResource((speed == 1) ? iconPauseResId: iconPlayResId);
view.setImageResource(play ? iconPauseResId : iconPlayResId );
}
/**

View File

@ -28,10 +28,17 @@
<include layout="@layout/toolbar_default" />
<FrameLayout
android:id="@+id/fragment_container"
<org.xbmc.kore.ui.widgets.NowPlayingPanel
xmlns:sothree="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/now_playing_panel"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
android:layout_height="match_parent"
android:gravity="bottom"
sothree:umanoPanelHeight="@dimen/notification_art_slim_height"
sothree:umanoShadowHeight="4dp"
sothree:umanoFadeColor="#00000000"
sothree:umanoInitialState="hidden"/>
</LinearLayout>
<fragment android:id="@+id/navigation_drawer"

View File

@ -169,12 +169,14 @@
style="@style/Widget.Button.Borderless"
android:src="?attr/iconVolumeMute"
android:contentDescription="@string/volume_mute"/>
<org.xbmc.kore.ui.widgets.VolumeLevelIndicator
android:id="@+id/volume_level_indicator"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:orientation="vertical"/>
android:orientation="vertical" />
<org.xbmc.kore.ui.widgets.RepeatModeButton
android:id="@+id/repeat"
android:layout_width="0dp"

View File

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Martijn Brekhof. 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="?attr/colorPrimaryDark">
<ImageView android:id="@+id/npp_poster"
android:layout_width="@dimen/notification_art_slim_width"
android:layout_height="@dimen/notification_art_slim_height"
android:layout_weight="0"
android:scaleType="centerInside"
android:contentDescription="@string/poster"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_art_slim_height"
android:orientation="vertical"
android:layout_weight="1">
<TextView
android:id="@+id/npp_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/small_padding"
android:paddingRight="@dimen/small_padding"
android:textAppearance="@style/TextAppearance.Notification.Title"
android:maxLines="1"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:gravity="center_vertical"/>
<TextView
android:id="@+id/npp_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/small_padding"
android:paddingRight="@dimen/small_padding"
android:textAppearance="@style/TextAppearance.Notification.Details"
android:maxLines="1"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:gravity="center_vertical"/>
</LinearLayout>
<org.xbmc.kore.ui.widgets.HighlightButton
android:id="@+id/npp_volume_muted_indicator"
android:layout_width="@dimen/notification_art_slim_height"
android:layout_height="@dimen/notification_art_slim_height"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconVolumeMute"
android:contentDescription="@string/volume_mute"
android:visibility="gone"/>
<ImageButton
android:id="@+id/npp_previous"
android:layout_width="@dimen/notification_art_slim_height"
android:layout_height="@dimen/notification_art_slim_height"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconPrevious"
android:contentDescription="@string/previous"/>
<ImageButton
android:id="@+id/npp_play"
android:layout_width="@dimen/notification_art_slim_height"
android:layout_height="@dimen/notification_art_slim_height"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconPlay"
android:contentDescription="@string/play"/>
<ImageButton
android:id="@+id/npp_next"
android:layout_width="@dimen/notification_art_slim_height"
android:layout_height="@dimen/notification_art_slim_height"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconNext"
android:contentDescription="@string/next"/>
</LinearLayout>
<org.xbmc.kore.ui.widgets.MediaProgressIndicator
android:id="@+id/npp_progress_indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="@dimen/default_padding"
android:paddingRight="@dimen/small_padding"
android:paddingLeft="@dimen/small_padding"
android:orientation="horizontal"
android:background="?attr/contentBackgroundColor"
style="@style/TextAppearance.Media.Progress"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/buttonbar_height"
android:orientation="horizontal"
style="@style/ButtonBar"
android:background="?attr/contentBackgroundColor">
<org.xbmc.kore.ui.widgets.HighlightButton
android:id="@+id/npp_volume_mute"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconVolumeMute"
android:contentDescription="@string/volume_mute"/>
<org.xbmc.kore.ui.widgets.VolumeLevelIndicator
android:id="@+id/npp_volume_level_indicator"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:orientation="vertical">
</org.xbmc.kore.ui.widgets.VolumeLevelIndicator>
<org.xbmc.kore.ui.widgets.RepeatModeButton
android:id="@+id/npp_repeat"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconRepeat"
android:contentDescription="@string/repeat"/>
<org.xbmc.kore.ui.widgets.HighlightButton
android:id="@+id/npp_shuffle"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
style="@style/Widget.Button.Borderless"
android:src="?attr/iconShuffle"
android:contentDescription="@string/shuffle"/>
</LinearLayout>
</LinearLayout>
</merge>

View File

@ -128,7 +128,8 @@
<dimen name="notification_expanded_height">128dp</dimen>
<dimen name="notification_art_default_height">64dp</dimen>
<dimen name="notification_art_default_width">64dp</dimen>
<dimen name="notification_art_slim_width">44dp</dimen>
<dimen name="notification_art_slim_width">31dp</dimen>
<dimen name="notification_art_slim_height">44dp</dimen>
<dimen name="notification_expanded_art_default_height">128dp</dimen>
<dimen name="notification_expanded_art_default_width">128dp</dimen>
<dimen name="notification_expanded_art_slim_width">88dp</dimen>

View File

@ -346,6 +346,8 @@
<string name="keep_remote_above_lockscreen">Show over lockscreen</string>
<string name="pref_keep_screen_on">Stay awake</string>
<string name="show_notification">Show notification while playing</string>
<string name="show_now_playing_panel">Show now playing panel while playing</string>
<string name="show_now_playing_panel_summary">Displays an expandable panel at the bottom of the screen when media is playing or paused</string>
<string name="pause_during_calls">Pause during phone call</string>
<string name="use_hardware_volume_keys">Use device volume keys</string>
<string name="vibrate_on_remote">Vibrate on touch</string>

View File

@ -64,6 +64,12 @@
android:title="@string/show_notification"
android:defaultValue="false"/>
<SwitchPreferenceCompat
android:key="pref_show_nowplayingpanel"
android:title="@string/show_now_playing_panel"
android:summary="@string/show_now_playing_panel_summary"
android:defaultValue="true"/>
<SwitchPreferenceCompat
android:key="pref_pause_during_calls"
android:title="@string/pause_during_calls"