Added NowPlayingFragment

This commit is contained in:
Nite 2021-02-08 20:24:20 +01:00
parent f0917820cb
commit cf90abb77e
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
23 changed files with 368 additions and 431 deletions

View File

@ -78,8 +78,8 @@ public class SubsonicTabActivity extends ResultActivity
private static final String STATE_ACTIVE_POSITION = "org.moire.ultrasonic.activePosition"; private static final String STATE_ACTIVE_POSITION = "org.moire.ultrasonic.activePosition";
private static final int DIALOG_ASK_FOR_SHARE_DETAILS = 102; private static final int DIALOG_ASK_FOR_SHARE_DETAILS = 102;
private Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class); private final Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
private Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class); private final Lazy<MediaPlayerLifecycleSupport> lifecycleSupport = inject(MediaPlayerLifecycleSupport.class);
protected Lazy<ImageLoaderProvider> imageLoader = inject(ImageLoaderProvider.class); protected Lazy<ImageLoaderProvider> imageLoader = inject(ImageLoaderProvider.class);
public MenuDrawer menuDrawer; public MenuDrawer menuDrawer;
@ -118,7 +118,7 @@ public class SubsonicTabActivity extends ResultActivity
bookmarksMenuItem = findViewById(R.id.menu_bookmarks); bookmarksMenuItem = findViewById(R.id.menu_bookmarks);
sharesMenuItem = findViewById(R.id.menu_shares); sharesMenuItem = findViewById(R.id.menu_shares);
setActionBarDisplayHomeAsUp(true); //setActionBarDisplayHomeAsUp(true);
TextView activeView = (TextView) findViewById(menuActiveViewId); TextView activeView = (TextView) findViewById(menuActiveViewId);
@ -163,11 +163,11 @@ public class SubsonicTabActivity extends ResultActivity
if (!nowPlayingHidden) if (!nowPlayingHidden)
{ {
showNowPlaying(); //showNowPlaying();
} }
else else
{ {
hideNowPlaying(); //hideNowPlaying();
} }
} }
@ -194,23 +194,6 @@ public class SubsonicTabActivity extends ResultActivity
imageLoader.getValue().clearImageLoader(); imageLoader.getValue().clearImageLoader();
} }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
boolean isVolumeDown = keyCode == KeyEvent.KEYCODE_VOLUME_DOWN;
boolean isVolumeUp = keyCode == KeyEvent.KEYCODE_VOLUME_UP;
boolean isVolumeAdjust = isVolumeDown || isVolumeUp;
boolean isJukebox = getMediaPlayerController() != null && getMediaPlayerController().isJukeboxEnabled();
if (isVolumeAdjust && isJukebox)
{
getMediaPlayerController().adjustJukeboxVolume(isVolumeUp);
return true;
}
return super.onKeyDown(keyCode, event);
}
protected void restart() protected void restart()
{ {
Intent intent = new Intent(this, this.getClass()); Intent intent = new Intent(this, this.getClass());
@ -246,270 +229,11 @@ public class SubsonicTabActivity extends ResultActivity
} }
} }
public void showNowPlaying()
{
this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
new SilentBackgroundTask<Void>(SubsonicTabActivity.this)
{
@Override
protected Void doInBackground() throws Throwable
{
if (!Util.getShowNowPlayingPreference(SubsonicTabActivity.this))
{
hideNowPlaying();
return null;
}
if (nowPlayingView != null)
{
PlayerState playerState = mediaPlayerControllerLazy.getValue().getPlayerState();
if (playerState.equals(PlayerState.PAUSED) || playerState.equals(PlayerState.STARTED))
{
DownloadFile file = mediaPlayerControllerLazy.getValue().getCurrentPlaying();
if (file != null)
{
final Entry song = file.getSong();
showNowPlaying(SubsonicTabActivity.this, mediaPlayerControllerLazy.getValue(), song, playerState);
}
}
else
{
hideNowPlaying();
}
}
return null;
}
@Override
protected void done(Void result)
{
}
}.execute();
}
});
}
private void showNowPlaying(final Context context, final MediaPlayerController mediaPlayerController, final Entry song, final PlayerState playerState)
{
if (context == null || mediaPlayerController == null || song == null || playerState == null)
{
return;
}
if (!Util.getShowNowPlayingPreference(context))
{
hideNowPlaying();
return;
}
if (nowPlayingView != null)
{
try
{
setVisibilityOnUiThread(nowPlayingView, View.VISIBLE);
nowPlayingHidden = false;
ImageView playButton = (ImageView) nowPlayingView.findViewById(R.id.now_playing_control_play);
if (playerState == PlayerState.PAUSED)
{
setImageDrawableOnUiThread(playButton, Util.getDrawableFromAttribute(context, R.attr.media_play));
}
else if (playerState == PlayerState.STARTED)
{
setImageDrawableOnUiThread(playButton, Util.getDrawableFromAttribute(context, R.attr.media_pause));
}
String title = song.getTitle();
String artist = song.getArtist();
final ImageView nowPlayingAlbumArtImage = (ImageView) nowPlayingView.findViewById(R.id.now_playing_image);
TextView nowPlayingTrack = (TextView) nowPlayingView.findViewById(R.id.now_playing_trackname);
TextView nowPlayingArtist = (TextView) nowPlayingView.findViewById(R.id.now_playing_artist);
this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
imageLoader.getValue().getImageLoader().loadImage(nowPlayingAlbumArtImage, song, false, Util.getNotificationImageSize(context), false, true);
}
});
// TODO: Refactor to use navigation
final Intent intent = new Intent(context, SelectAlbumFragment.class);// SelectAlbumActivity.class);
if (Util.getShouldUseId3Tags(context))
{
intent.putExtra(Constants.INTENT_EXTRA_NAME_IS_ALBUM, true);
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, song.getAlbumId());
}
else
{
intent.putExtra(Constants.INTENT_EXTRA_NAME_IS_ALBUM, false);
intent.putExtra(Constants.INTENT_EXTRA_NAME_ID, song.getParent());
}
intent.putExtra(Constants.INTENT_EXTRA_NAME_NAME, song.getAlbum());
setOnClickListenerOnUiThread(nowPlayingAlbumArtImage, new OnClickListener()
{
@Override
public void onClick(View view)
{
startActivityForResultWithoutTransition(SubsonicTabActivity.this, intent);
}
});
setTextOnUiThread(nowPlayingTrack, title);
setTextOnUiThread(nowPlayingArtist, artist);
ImageView nowPlayingControlPlay = (ImageView) nowPlayingView.findViewById(R.id.now_playing_control_play);
SwipeDetector swipeDetector = new SwipeDetector(SubsonicTabActivity.this, mediaPlayerController);
setOnTouchListenerOnUiThread(nowPlayingView, swipeDetector);
setOnClickListenerOnUiThread(nowPlayingView, new OnClickListener()
{
@Override
public void onClick(View v)
{
}
});
setOnClickListenerOnUiThread(nowPlayingControlPlay, new OnClickListener()
{
@Override
public void onClick(View view)
{
mediaPlayerController.togglePlayPause();
}
});
}
catch (Exception x)
{
Timber.w(x, "Failed to get notification cover art");
}
}
}
public void hideNowPlaying()
{
try
{
if (nowPlayingView != null)
{
setVisibilityOnUiThread(nowPlayingView, View.GONE);
}
}
catch (Exception ex)
{
Timber.w(ex, "Exception in hideNowPlaying");
}
}
public void setOnTouchListenerOnUiThread(final View view, final OnTouchListener listener)
{
this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
if (view != null && view.getVisibility() != View.GONE)
{
view.setOnTouchListener(listener);
}
}
});
}
public void setOnClickListenerOnUiThread(final View view, final OnClickListener listener)
{
this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
if (view != null && view.getVisibility() != View.GONE)
{
view.setOnClickListener(listener);
}
}
});
}
public void setTextOnUiThread(final TextView view, final CharSequence text)
{
this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
if (view != null && view.getVisibility() != View.GONE)
{
view.setText(text);
}
}
});
}
public void setImageDrawableOnUiThread(final ImageView view, final Drawable drawable)
{
this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
if (view != null && view.getVisibility() != View.GONE)
{
view.setImageDrawable(drawable);
}
}
});
}
public void setVisibilityOnUiThread(final View view, final int visibility)
{
this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
if (view != null && view.getVisibility() != visibility)
{
view.setVisibility(visibility);
}
}
});
}
public static SubsonicTabActivity getInstance() public static SubsonicTabActivity getInstance()
{ {
return instance; return instance;
} }
public MediaPlayerController getMediaPlayerController()
{
return mediaPlayerControllerLazy.getValue();
}
protected void setActionBarDisplayHomeAsUp(boolean enabled)
{
ActionBar actionBar = getSupportActionBar();
if (actionBar != null)
{
actionBar.setDisplayHomeAsUpEnabled(enabled);
}
}
@Override @Override
protected void onRestoreInstanceState(Bundle inState) protected void onRestoreInstanceState(Bundle inState)
@ -527,87 +251,5 @@ public class SubsonicTabActivity extends ResultActivity
outState.putInt(STATE_ACTIVE_POSITION, activePosition); outState.putInt(STATE_ACTIVE_POSITION, activePosition);
} }
@Override
public void onBackPressed()
{
final int drawerState = menuDrawer.getDrawerState();
if (drawerState == MenuDrawer.STATE_OPEN || drawerState == MenuDrawer.STATE_OPENING)
{
menuDrawer.closeMenu(true);
return;
}
super.onBackPressed();
}
protected class SwipeDetector implements OnTouchListener
{
public SwipeDetector(SubsonicTabActivity activity, final MediaPlayerController mediaPlayerController)
{
this.mediaPlayerController = mediaPlayerController;
this.activity = activity;
}
private static final int MIN_DISTANCE = 30;
private float downX, downY, upX, upY;
private MediaPlayerController mediaPlayerController;
private SubsonicTabActivity activity;
@Override
public boolean onTouch(View v, MotionEvent event)
{
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
downX = event.getX();
downY = event.getY();
return false;
}
case MotionEvent.ACTION_UP:
{
upX = event.getX();
upY = event.getY();
float deltaX = downX - upX;
float deltaY = downY - upY;
if (Math.abs(deltaX) > MIN_DISTANCE)
{
// left or right
if (deltaX < 0)
{
mediaPlayerController.previous();
return false;
}
if (deltaX > 0)
{
mediaPlayerController.next();
return false;
}
}
else if (Math.abs(deltaY) > MIN_DISTANCE)
{
if (deltaY < 0)
{
SubsonicTabActivity.nowPlayingHidden = true;
activity.hideNowPlaying();
return false;
}
if (deltaY > 0)
{
return false;
}
}
// TODO: Refactor this to Navigation. It should automatically go to the PlayerFragment.
//SubsonicTabActivity.this.startActivityForResultWithoutTransition(activity, DownloadActivity.class);
return false;
}
}
return false;
}
}
} }

View File

@ -28,6 +28,7 @@ import org.moire.ultrasonic.domain.ChatMessage;
import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.service.MusicServiceFactory;
import org.moire.ultrasonic.util.BackgroundTask; import org.moire.ultrasonic.util.BackgroundTask;
import org.moire.ultrasonic.util.CancellationToken;
import org.moire.ultrasonic.util.TabActivityBackgroundTask; import org.moire.ultrasonic.util.TabActivityBackgroundTask;
import org.moire.ultrasonic.util.Util; import org.moire.ultrasonic.util.Util;
import org.moire.ultrasonic.view.ChatAdapter; import org.moire.ultrasonic.view.ChatAdapter;
@ -50,6 +51,7 @@ public class ChatFragment extends Fragment {
private Timer timer; private Timer timer;
private volatile static Long lastChatMessageTime = (long) 0; private volatile static Long lastChatMessageTime = (long) 0;
private static final ArrayList<ChatMessage> messageList = new ArrayList<ChatMessage>(); private static final ArrayList<ChatMessage> messageList = new ArrayList<ChatMessage>();
private CancellationToken cancellationToken;
private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class); private final Lazy<ActiveServerProvider> activeServerProvider = inject(ActiveServerProvider.class);
@ -67,6 +69,8 @@ public class ChatFragment extends Fragment {
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
cancellationToken = new CancellationToken();
messageEditText = view.findViewById(R.id.chat_edittext); messageEditText = view.findViewById(R.id.chat_edittext);
sendButton = view.findViewById(R.id.chat_send); sendButton = view.findViewById(R.id.chat_send);
@ -184,6 +188,12 @@ public class ChatFragment extends Fragment {
} }
} }
@Override
public void onDestroyView() {
cancellationToken.cancel();
super.onDestroyView();
}
private void timerMethod() private void timerMethod()
{ {
int refreshInterval = Util.getChatRefreshInterval(getContext()); int refreshInterval = Util.getChatRefreshInterval(getContext());
@ -228,7 +238,7 @@ public class ChatFragment extends Fragment {
{ {
messageEditText.setText(""); messageEditText.setText("");
BackgroundTask<Void> task = new TabActivityBackgroundTask<Void>(getActivity(), false) BackgroundTask<Void> task = new TabActivityBackgroundTask<Void>(getActivity(), false, null, cancellationToken)
{ {
@Override @Override
protected Void doInBackground() throws Throwable protected Void doInBackground() throws Throwable
@ -252,7 +262,7 @@ public class ChatFragment extends Fragment {
private synchronized void load() private synchronized void load()
{ {
BackgroundTask<List<ChatMessage>> task = new TabActivityBackgroundTask<List<ChatMessage>>(getActivity(), false) BackgroundTask<List<ChatMessage>> task = new TabActivityBackgroundTask<List<ChatMessage>>(getActivity(), false, null, cancellationToken)
{ {
@Override @Override
protected List<ChatMessage> doInBackground() throws Throwable protected List<ChatMessage> doInBackground() throws Throwable

View File

@ -0,0 +1,205 @@
package org.moire.ultrasonic.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import org.moire.ultrasonic.R;
import org.moire.ultrasonic.domain.MusicDirectory;
import org.moire.ultrasonic.domain.PlayerState;
import org.moire.ultrasonic.service.DownloadFile;
import org.moire.ultrasonic.service.MediaPlayerController;
import org.moire.ultrasonic.subsonic.ImageLoaderProvider;
import org.moire.ultrasonic.util.Constants;
import org.moire.ultrasonic.util.NowPlayingEventDistributor;
import org.moire.ultrasonic.util.NowPlayingEventListener;
import org.moire.ultrasonic.util.Util;
import kotlin.Lazy;
import timber.log.Timber;
import static org.koin.java.KoinJavaComponent.inject;
public class NowPlayingFragment extends Fragment {
private static final int MIN_DISTANCE = 30;
private float downX;
private float downY;
ImageView playButton;
ImageView nowPlayingAlbumArtImage;
TextView nowPlayingTrack;
TextView nowPlayingArtist;
private final Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
private final Lazy<ImageLoaderProvider> imageLoader = inject(ImageLoaderProvider.class);
private final Lazy<NowPlayingEventDistributor> nowPlayingEventDistributor = inject(NowPlayingEventDistributor.class);
private NowPlayingEventListener nowPlayingEventListener;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
Util.applyTheme(this.getContext());
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.now_playing, container, false);
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) {
playButton = (ImageView) view.findViewById(R.id.now_playing_control_play);
nowPlayingAlbumArtImage = (ImageView) view.findViewById(R.id.now_playing_image);
nowPlayingTrack = (TextView) view.findViewById(R.id.now_playing_trackname);
nowPlayingArtist = (TextView) view.findViewById(R.id.now_playing_artist);
nowPlayingEventListener = new NowPlayingEventListener() {
@Override
public void onDismissNowPlaying() { }
@Override
public void onHideNowPlaying() { }
@Override
public void onShowNowPlaying() { Update(); }
};
nowPlayingEventDistributor.getValue().subscribe(nowPlayingEventListener);
}
@Override
public void onResume() {
super.onResume();
Update();
}
@Override
public void onDestroy() {
super.onDestroy();
nowPlayingEventDistributor.getValue().unsubscribe(nowPlayingEventListener);
}
private void Update() {
try
{
PlayerState playerState = mediaPlayerControllerLazy.getValue().getPlayerState();
if (playerState == PlayerState.PAUSED) {
playButton.setImageDrawable(Util.getDrawableFromAttribute(getContext(), R.attr.media_play));
} else if (playerState == PlayerState.STARTED) {
playButton.setImageDrawable(Util.getDrawableFromAttribute(getContext(), R.attr.media_pause));
}
DownloadFile file = mediaPlayerControllerLazy.getValue().getCurrentPlaying();
if (file != null) {
final MusicDirectory.Entry song = file.getSong();
String title = song.getTitle();
String artist = song.getArtist();
imageLoader.getValue().getImageLoader().loadImage(nowPlayingAlbumArtImage, song, false, Util.getNotificationImageSize(getContext()), false, true);
nowPlayingTrack.setText(title);
nowPlayingArtist.setText(artist);
nowPlayingAlbumArtImage.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Bundle bundle = new Bundle();
if (Util.getShouldUseId3Tags(getContext())) {
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, true);
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, song.getAlbumId());
} else {
bundle.putBoolean(Constants.INTENT_EXTRA_NAME_IS_ALBUM, false);
bundle.putString(Constants.INTENT_EXTRA_NAME_ID, song.getParent());
}
bundle.putString(Constants.INTENT_EXTRA_NAME_NAME, song.getAlbum());
Navigation.findNavController(getView()).navigate(R.id.selectAlbumFragment, bundle);
}
});
}
getView().setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return handleOnTouch(v, event);
}
});
// TODO: Check if this empty onClickListener is necessary
getView().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
playButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mediaPlayerControllerLazy.getValue().togglePlayPause();
}
});
}
catch (Exception x) {
Timber.w(x, "Failed to get notification cover art");
}
}
private boolean handleOnTouch(View v, MotionEvent event) {
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
{
downX = event.getX();
downY = event.getY();
return false;
}
case MotionEvent.ACTION_UP:
{
float upX = event.getX();
float upY = event.getY();
float deltaX = downX - upX;
float deltaY = downY - upY;
if (Math.abs(deltaX) > MIN_DISTANCE)
{
// left or right
if (deltaX < 0)
{
mediaPlayerControllerLazy.getValue().previous();
return false;
}
if (deltaX > 0)
{
mediaPlayerControllerLazy.getValue().next();
return false;
}
}
else if (Math.abs(deltaY) > MIN_DISTANCE)
{
if (deltaY < 0)
{
nowPlayingEventDistributor.getValue().RaiseNowPlayingDismissedEvent();
return false;
}
if (deltaY > 0)
{
return false;
}
}
Navigation.findNavController(getView()).navigate(R.id.playerFragment);
return false;
}
}
return false;
}
}

View File

@ -1404,6 +1404,7 @@ public class PlayerFragment extends Fragment implements GestureDetector.OnGestur
@Override @Override
protected void done(final Void result) protected void done(final Void result)
{ {
if (cancellationToken.isCancellationRequested()) return;
if (currentPlaying != null) if (currentPlaying != null)
{ {
final int millisTotal = duration == null ? 0 : duration; final int millisTotal = duration == null ? 0 : duration;

View File

@ -17,6 +17,7 @@ import org.moire.ultrasonic.domain.PodcastsChannel;
import org.moire.ultrasonic.service.MusicService; import org.moire.ultrasonic.service.MusicService;
import org.moire.ultrasonic.service.MusicServiceFactory; import org.moire.ultrasonic.service.MusicServiceFactory;
import org.moire.ultrasonic.util.BackgroundTask; import org.moire.ultrasonic.util.BackgroundTask;
import org.moire.ultrasonic.util.CancellationToken;
import org.moire.ultrasonic.util.Constants; import org.moire.ultrasonic.util.Constants;
import org.moire.ultrasonic.util.TabActivityBackgroundTask; import org.moire.ultrasonic.util.TabActivityBackgroundTask;
import org.moire.ultrasonic.util.Util; import org.moire.ultrasonic.util.Util;
@ -28,6 +29,7 @@ public class PodcastFragment extends Fragment {
private View emptyTextView; private View emptyTextView;
ListView channelItemsListView = null; ListView channelItemsListView = null;
private CancellationToken cancellationToken;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
@ -43,6 +45,8 @@ public class PodcastFragment extends Fragment {
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
cancellationToken = new CancellationToken();
FragmentTitle.Companion.setTitle(this, R.string.podcasts_label); FragmentTitle.Companion.setTitle(this, R.string.podcasts_label);
emptyTextView = view.findViewById(R.id.select_podcasts_empty); emptyTextView = view.findViewById(R.id.select_podcasts_empty);
@ -65,9 +69,15 @@ public class PodcastFragment extends Fragment {
load(); load();
} }
@Override
public void onDestroyView() {
cancellationToken.cancel();
super.onDestroyView();
}
private void load() private void load()
{ {
BackgroundTask<List<PodcastsChannel>> task = new TabActivityBackgroundTask<List<PodcastsChannel>>(getActivity(), true) BackgroundTask<List<PodcastsChannel>> task = new TabActivityBackgroundTask<List<PodcastsChannel>>(getActivity(), true, null, cancellationToken)
{ {
@Override @Override
protected List<PodcastsChannel> doInBackground() throws Throwable protected List<PodcastsChannel> doInBackground() throws Throwable

View File

@ -32,6 +32,7 @@ import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X2;
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3; import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3;
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4; import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4;
import org.moire.ultrasonic.util.FileUtil; import org.moire.ultrasonic.util.FileUtil;
import org.moire.ultrasonic.util.NowPlayingEventDistributor;
import org.moire.ultrasonic.util.ShufflePlayBuffer; import org.moire.ultrasonic.util.ShufflePlayBuffer;
import org.moire.ultrasonic.util.SimpleServiceBinder; import org.moire.ultrasonic.util.SimpleServiceBinder;
import org.moire.ultrasonic.util.Util; import org.moire.ultrasonic.util.Util;
@ -64,10 +65,11 @@ public class MediaPlayerService extends Service
private final Scrobbler scrobbler = new Scrobbler(); private final Scrobbler scrobbler = new Scrobbler();
public Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class); public Lazy<JukeboxMediaPlayer> jukeboxMediaPlayer = inject(JukeboxMediaPlayer.class);
private Lazy<DownloadQueueSerializer> downloadQueueSerializerLazy = inject(DownloadQueueSerializer.class); private final Lazy<DownloadQueueSerializer> downloadQueueSerializerLazy = inject(DownloadQueueSerializer.class);
private Lazy<ShufflePlayBuffer> shufflePlayBufferLazy = inject(ShufflePlayBuffer.class); private final Lazy<ShufflePlayBuffer> shufflePlayBufferLazy = inject(ShufflePlayBuffer.class);
private Lazy<Downloader> downloaderLazy = inject(Downloader.class); private final Lazy<Downloader> downloaderLazy = inject(Downloader.class);
private Lazy<LocalMediaPlayer> localMediaPlayerLazy = inject(LocalMediaPlayer.class); private final Lazy<LocalMediaPlayer> localMediaPlayerLazy = inject(LocalMediaPlayer.class);
private final Lazy<NowPlayingEventDistributor> nowPlayingEventDistributor = inject(NowPlayingEventDistributor.class);
private LocalMediaPlayer localMediaPlayer; private LocalMediaPlayer localMediaPlayer;
private Downloader downloader; private Downloader downloader;
private ShufflePlayBuffer shufflePlayBuffer; private ShufflePlayBuffer shufflePlayBuffer;
@ -280,21 +282,14 @@ public class MediaPlayerService extends Service
UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
if (currentPlaying != null) if (currentPlaying != null)
{ {
updateNotification(localMediaPlayer.playerState, currentPlaying); updateNotification(localMediaPlayer.playerState, currentPlaying);
if (tabInstance != null) { nowPlayingEventDistributor.getValue().RaiseShowNowPlayingEvent();
tabInstance.showNowPlaying();
}
} }
else else
{ {
if (tabInstance != null) nowPlayingEventDistributor.getValue().RaiseHideNowPlayingEvent();
{
tabInstance.hideNowPlaying();
}
stopForeground(true); stopForeground(true);
localMediaPlayer.clearRemoteControl(); localMediaPlayer.clearRemoteControl();
isInForeground = false; isInForeground = false;
@ -499,7 +494,6 @@ public class MediaPlayerService extends Service
UltrasonicAppWidgetProvider4X2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true); UltrasonicAppWidgetProvider4X2.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, true);
UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); UltrasonicAppWidgetProvider4X3.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false); UltrasonicAppWidgetProvider4X4.getInstance().notifyChange(MediaPlayerService.this, song, playerState == PlayerState.STARTED, false);
SubsonicTabActivity tabInstance = SubsonicTabActivity.getInstance();
if (show) if (show)
{ {
@ -507,18 +501,12 @@ public class MediaPlayerService extends Service
if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED) if (playerState == PlayerState.STARTED || playerState == PlayerState.PAUSED)
{ {
updateNotification(playerState, currentPlaying); updateNotification(playerState, currentPlaying);
if (tabInstance != null) nowPlayingEventDistributor.getValue().RaiseShowNowPlayingEvent();
{
tabInstance.showNowPlaying();
}
} }
} }
else else
{ {
if (tabInstance != null) nowPlayingEventDistributor.getValue().RaiseHideNowPlayingEvent();
{
tabInstance.hideNowPlaying();
}
stopForeground(true); stopForeground(true);
localMediaPlayer.clearRemoteControl(); localMediaPlayer.clearRemoteControl();
isInForeground = false; isInForeground = false;

View File

@ -10,18 +10,9 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
*/ */
public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T> public abstract class TabActivityBackgroundTask<T> extends BackgroundTask<T>
{ {
private final boolean changeProgress; private final boolean changeProgress;
private final SwipeRefreshLayout swipe; private final SwipeRefreshLayout swipe;
private CancellationToken cancel; private final CancellationToken cancel;
// TODO: Try to remove this constructor
public TabActivityBackgroundTask(Activity activity, boolean changeProgress)
{
super(activity);
this.changeProgress = changeProgress;
this.swipe = null;
}
public TabActivityBackgroundTask(Activity activity, boolean changeProgress, public TabActivityBackgroundTask(Activity activity, boolean changeProgress,
SwipeRefreshLayout swipe, CancellationToken cancel) SwipeRefreshLayout swipe, CancellationToken cancel)

View File

@ -12,9 +12,11 @@ import android.provider.SearchRecentSuggestions
import android.view.KeyEvent import android.view.KeyEvent
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.FragmentContainerView
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
@ -28,12 +30,16 @@ import org.koin.android.ext.android.inject
import org.koin.android.viewmodel.ext.android.viewModel import org.koin.android.viewmodel.ext.android.viewModel
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.provider.SearchSuggestionProvider import org.moire.ultrasonic.provider.SearchSuggestionProvider
import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.service.MediaPlayerController
import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport import org.moire.ultrasonic.service.MediaPlayerLifecycleSupport
import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.FileUtil
import org.moire.ultrasonic.util.NowPlayingEventDistributor
import org.moire.ultrasonic.util.NowPlayingEventListener
import org.moire.ultrasonic.util.SubsonicUncaughtExceptionHandler import org.moire.ultrasonic.util.SubsonicUncaughtExceptionHandler
import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.util.Util
import timber.log.Timber import timber.log.Timber
@ -47,14 +53,20 @@ class NavigationActivity : AppCompatActivity() {
var bookmarksMenuItem: MenuItem? = null var bookmarksMenuItem: MenuItem? = null
var sharesMenuItem: MenuItem? = null var sharesMenuItem: MenuItem? = null
private var theme: String? = null private var theme: String? = null
var nowPlayingView: FragmentContainerView? = null
var nowPlayingHidden = false
private lateinit var appBarConfiguration : AppBarConfiguration private lateinit var appBarConfiguration : AppBarConfiguration
private lateinit var nowPlayingEventListener : NowPlayingEventListener
private val serverSettingsModel: ServerSettingsModel by viewModel() private val serverSettingsModel: ServerSettingsModel by viewModel()
private val lifecycleSupport: MediaPlayerLifecycleSupport by inject() private val lifecycleSupport: MediaPlayerLifecycleSupport by inject()
private val mediaPlayerController: MediaPlayerController by inject() private val mediaPlayerController: MediaPlayerController by inject()
private val imageLoaderProvider: ImageLoaderProvider by inject() private val imageLoaderProvider: ImageLoaderProvider by inject()
private val nowPlayingEventDistributor: NowPlayingEventDistributor by inject()
private var infoDialogDisplayed = false private var infoDialogDisplayed = false
private var currentFragmentId: Int = 0
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setUncaughtExceptionHandler() setUncaughtExceptionHandler()
@ -64,6 +76,7 @@ class NavigationActivity : AppCompatActivity() {
volumeControlStream = AudioManager.STREAM_MUSIC volumeControlStream = AudioManager.STREAM_MUSIC
setContentView(R.layout.navigation_activity) setContentView(R.layout.navigation_activity)
nowPlayingView = findViewById(R.id.now_playing_fragment)
val toolbar = findViewById<Toolbar>(R.id.toolbar) val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
@ -93,7 +106,15 @@ class NavigationActivity : AppCompatActivity() {
} }
Timber.d("Navigated to $dest") Timber.d("Navigated to $dest")
// TODO: Maybe we can find a better place for theme change. Currently the change occures when navigating between fragments currentFragmentId = destination.id
// Handle the hiding of the NowPlaying fragment when the Player is active
if (currentFragmentId == R.id.playerFragment) {
hideNowPlaying()
} else {
showNowPlaying()
}
// TODO: Maybe we can find a better place for theme change. Currently the change occurs when navigating between fragments
// but theoretically Settings could request a Navigation Activity recreate instantly when the theme setting changes // but theoretically Settings could request a Navigation Activity recreate instantly when the theme setting changes
// Make sure to update theme if it has changed // Make sure to update theme if it has changed
if (theme == null) theme = Util.getTheme(this) if (theme == null) theme = Util.getTheme(this)
@ -112,6 +133,24 @@ class NavigationActivity : AppCompatActivity() {
loadSettings() loadSettings()
showInfoDialog(showWelcomeScreen) showInfoDialog(showWelcomeScreen)
nowPlayingEventListener = object : NowPlayingEventListener {
override fun onDismissNowPlaying() {
// TODO: When will it be set back to false?
nowPlayingHidden = true;
hideNowPlaying();
}
override fun onHideNowPlaying() {
hideNowPlaying()
}
override fun onShowNowPlaying() {
showNowPlaying()
}
}
nowPlayingEventDistributor.subscribe(nowPlayingEventListener)
} }
override fun onResume() { override fun onResume() {
@ -125,19 +164,16 @@ class NavigationActivity : AppCompatActivity() {
// Lifecycle support's constructor registers some event receivers so it should be created early // Lifecycle support's constructor registers some event receivers so it should be created early
lifecycleSupport.onCreate() lifecycleSupport.onCreate()
// TODO: Implement NowPlaying as a Fragment if (!nowPlayingHidden) {
// This must be filled here because onCreate is called before the derived objects would call setContentView showNowPlaying()
//getNowPlayingView()
if (!SubsonicTabActivity.nowPlayingHidden) {
//showNowPlaying()
} else { } else {
//hideNowPlaying() hideNowPlaying()
} }
} }
override fun onDestroy() { override fun onDestroy() {
Util.unregisterMediaButtonEventReceiver(this, false) Util.unregisterMediaButtonEventReceiver(this, false)
nowPlayingEventDistributor.unsubscribe(nowPlayingEventListener)
super.onDestroy() super.onDestroy()
// TODO: Handle NowPlaying if necessary // TODO: Handle NowPlaying if necessary
@ -259,4 +295,34 @@ class NavigationActivity : AppCompatActivity() {
Thread.setDefaultUncaughtExceptionHandler(SubsonicUncaughtExceptionHandler(this)) Thread.setDefaultUncaughtExceptionHandler(SubsonicUncaughtExceptionHandler(this))
} }
} }
private fun showNowPlaying() {
if (!Util.getShowNowPlayingPreference(this) || nowPlayingHidden) {
hideNowPlaying()
return
}
// Do not show for Player fragment
if (currentFragmentId == R.id.playerFragment) {
hideNowPlaying()
return
}
if (nowPlayingView != null) {
val playerState: PlayerState = mediaPlayerController.playerState
if (playerState == PlayerState.PAUSED || playerState == PlayerState.STARTED) {
val file: DownloadFile? = mediaPlayerController.currentPlaying
if (file != null) {
val song = file.song
nowPlayingView?.visibility = View.VISIBLE
}
} else {
hideNowPlaying()
}
}
}
private fun hideNowPlaying() {
nowPlayingView?.visibility = View.GONE
}
} }

View File

@ -7,10 +7,12 @@ import org.moire.ultrasonic.cache.AndroidDirectories
import org.moire.ultrasonic.cache.Directories import org.moire.ultrasonic.cache.Directories
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.subsonic.ImageLoaderProvider import org.moire.ultrasonic.subsonic.ImageLoaderProvider
import org.moire.ultrasonic.util.NowPlayingEventDistributor
import org.moire.ultrasonic.util.PermissionUtil import org.moire.ultrasonic.util.PermissionUtil
val applicationModule = module { val applicationModule = module {
single { ActiveServerProvider(get(), androidContext()) } single { ActiveServerProvider(get(), androidContext()) }
single { ImageLoaderProvider(androidContext()) } single { ImageLoaderProvider(androidContext()) }
single { PermissionUtil(androidContext()) } single { PermissionUtil(androidContext()) }
single { NowPlayingEventDistributor() }
} }

View File

@ -0,0 +1,25 @@
package org.moire.ultrasonic.util
class NowPlayingEventDistributor {
var eventListenerList: MutableList<NowPlayingEventListener> = listOf<NowPlayingEventListener>().toMutableList()
fun subscribe(listener: NowPlayingEventListener) {
eventListenerList.add(listener)
}
fun unsubscribe(listener: NowPlayingEventListener) {
eventListenerList.remove(listener)
}
fun RaiseShowNowPlayingEvent() {
eventListenerList.forEach{ listener -> listener.onShowNowPlaying() }
}
fun RaiseHideNowPlayingEvent() {
eventListenerList.forEach{ listener -> listener.onHideNowPlaying() }
}
fun RaiseNowPlayingDismissedEvent() {
eventListenerList.forEach{ listener -> listener.onDismissNowPlaying() }
}
}

View File

@ -0,0 +1,7 @@
package org.moire.ultrasonic.util
interface NowPlayingEventListener {
fun onDismissNowPlaying()
fun onHideNowPlaying()
fun onShowNowPlaying()
}

View File

@ -46,6 +46,4 @@
</LinearLayout> </LinearLayout>
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>

View File

@ -60,7 +60,5 @@
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include layout="@layout/now_playing"/>
</LinearLayout> </LinearLayout>

View File

@ -15,6 +15,4 @@
a:layout_width="0dp" a:layout_width="0dp"
a:layout_height="0dp" /> a:layout_height="0dp" />
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>

View File

@ -8,7 +8,7 @@
a:layout_height="match_parent" a:layout_height="match_parent"
tools:context="org.moire.ultrasonic.activity.NavigationActivity"> tools:context="org.moire.ultrasonic.activity.NavigationActivity">
<LinearLayout <RelativeLayout
a:layout_width="match_parent" a:layout_width="match_parent"
a:layout_height="match_parent" a:layout_height="match_parent"
a:orientation="vertical"> a:orientation="vertical">
@ -18,14 +18,25 @@
a:layout_width="match_parent" a:layout_width="match_parent"
a:layout_height="wrap_content" /> a:layout_height="wrap_content" />
<fragment <androidx.fragment.app.FragmentContainerView
a:id="@+id/nav_host_fragment" a:id="@+id/nav_host_fragment"
a:name="androidx.navigation.fragment.NavHostFragment" a:name="androidx.navigation.fragment.NavHostFragment"
a:layout_width="match_parent" a:layout_width="match_parent"
a:layout_height="match_parent" a:layout_height="match_parent"
a:layout_above="@+id/now_playing_fragment"
a:layout_below="@+id/toolbar"
app:defaultNavHost="true" app:defaultNavHost="true"
app:navGraph="@navigation/navigation_graph" /> app:navGraph="@navigation/navigation_graph" />
</LinearLayout>
<androidx.fragment.app.FragmentContainerView
a:id="@+id/now_playing_fragment"
a:name="org.moire.ultrasonic.fragment.NowPlayingFragment"
a:layout_width="match_parent"
a:layout_height="wrap_content"
a:layout_alignParentBottom="true"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation_graph" />
</RelativeLayout>
<com.google.android.material.navigation.NavigationView <com.google.android.material.navigation.NavigationView
a:id="@+id/nav_view" a:id="@+id/nav_view"

View File

@ -3,8 +3,7 @@
android:id="@+id/now_playing" android:id="@+id/now_playing"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical" >
android:visibility="gone" >
<LinearLayout <LinearLayout
android:layout_height="4dip" android:layout_height="4dip"

View File

@ -22,6 +22,4 @@
a:fastScrollEnabled="true" a:fastScrollEnabled="true"
a:textFilterEnabled="true" /> a:textFilterEnabled="true" />
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>

View File

@ -18,6 +18,4 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>

View File

@ -33,6 +33,4 @@
<include layout="@layout/album_buttons" /> <include layout="@layout/album_buttons" />
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>

View File

@ -32,6 +32,4 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>

View File

@ -28,6 +28,4 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>

View File

@ -28,6 +28,4 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>

View File

@ -29,6 +29,4 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include layout="@layout/now_playing" />
</LinearLayout> </LinearLayout>