Merge pull request #3954 from ByteHamster/audioplayer-fragment

Convert AudioPlayer to fragment
This commit is contained in:
H. Lehmann 2020-03-25 22:49:34 +01:00 committed by GitHub
commit b5836650d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 1250 additions and 1173 deletions

View File

@ -32,7 +32,9 @@ import java.util.concurrent.TimeoutException;
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot; import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText; import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.allOf;
@ -193,4 +195,8 @@ public class EspressoTestUtils {
} }
androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().waitForIdleSync(); androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().waitForIdleSync();
} }
public static Matcher<View> actionBarOverflow() {
return allOf(isDisplayed(), withContentDescription("More options"));
}
} }

View File

@ -1,6 +1,7 @@
package de.test.antennapod.ui; package de.test.antennapod.ui;
import android.content.Intent; import android.content.Intent;
import android.view.View;
import androidx.test.espresso.Espresso; import androidx.test.espresso.Espresso;
import androidx.test.espresso.intent.rule.IntentsTestRule; import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
@ -8,6 +9,7 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.QueueFragment;
import de.test.antennapod.EspressoTestUtils; import de.test.antennapod.EspressoTestUtils;
import org.hamcrest.Matcher;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -18,6 +20,7 @@ import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withClassName; import static androidx.test.espresso.matcher.ViewMatchers.withClassName;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withText; import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static de.test.antennapod.NthMatcher.first;
import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.endsWith; import static org.hamcrest.CoreMatchers.endsWith;
@ -48,14 +51,14 @@ public class QueueFragmentTest {
@Test @Test
public void testSortEmptyQueue() { public void testSortEmptyQueue() {
Espresso.openContextualActionModeOverflowMenu(); onView(first(EspressoTestUtils.actionBarOverflow())).perform(click());
onView(withText(R.string.sort)).perform(click()); onView(withText(R.string.sort)).perform(click());
onView(withText(R.string.random)).perform(click()); onView(withText(R.string.random)).perform(click());
} }
@Test @Test
public void testKeepEmptyQueueSorted() { public void testKeepEmptyQueueSorted() {
Espresso.openContextualActionModeOverflowMenu(); onView(first(EspressoTestUtils.actionBarOverflow())).perform(click());
onView(withText(R.string.sort)).perform(click()); onView(withText(R.string.sort)).perform(click());
onView(withText(R.string.keep_sorted)).perform(click()); onView(withText(R.string.keep_sorted)).perform(click());
} }

View File

@ -7,12 +7,14 @@ import android.preference.PreferenceManager;
import androidx.test.rule.ActivityTestRule; import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4; import androidx.test.runner.AndroidJUnit4;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AudioplayerActivity; import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.QueueFragment; import de.danoeh.antennapod.fragment.QueueFragment;
import de.test.antennapod.EspressoTestUtils; import de.test.antennapod.EspressoTestUtils;
import de.test.antennapod.IgnoreOnCi; import de.test.antennapod.IgnoreOnCi;
@ -34,6 +36,8 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText; import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static de.test.antennapod.EspressoTestUtils.waitForView; import static de.test.antennapod.EspressoTestUtils.waitForView;
import static de.test.antennapod.NthMatcher.first;
import static org.hamcrest.Matchers.allOf;
/** /**
* User interface tests for changing the playback speed. * User interface tests for changing the playback speed.
@ -43,10 +47,10 @@ import static de.test.antennapod.EspressoTestUtils.waitForView;
public class SpeedChangeTest { public class SpeedChangeTest {
@Rule @Rule
public ActivityTestRule<AudioplayerActivity> activityRule public ActivityTestRule<MainActivity> activityRule = new ActivityTestRule<>(MainActivity.class, false, false);
= new ActivityTestRule<>(AudioplayerActivity.class, false, false);
private UITestUtils uiTestUtils; private UITestUtils uiTestUtils;
private String[] availableSpeeds; private String[] availableSpeeds;
private PlaybackController controller;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@ -70,7 +74,10 @@ public class SpeedChangeTest {
UserPreferences.setPlaybackSpeedArray(availableSpeeds); UserPreferences.setPlaybackSpeedArray(availableSpeeds);
EspressoTestUtils.tryKillPlaybackService(); EspressoTestUtils.tryKillPlaybackService();
activityRule.launchActivity(new Intent()); activityRule.launchActivity(new Intent().putExtra(MainActivity.EXTRA_OPEN_PLAYER, true));
controller = new PlaybackController(activityRule.getActivity(), true);
controller.init();
controller.getMedia(); // To load media
} }
@After @After
@ -86,21 +93,21 @@ public class SpeedChangeTest {
@Test @Test
public void testChangeSpeedPlaying() { public void testChangeSpeedPlaying() {
onView(isRoot()).perform(waitForView(withId(R.id.butPlay), 1000)); onView(isRoot()).perform(waitForView(withId(R.id.butPlay), 1000));
onView(withId(R.id.butPlay)).perform(click()); controller.playPause();
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() Awaitility.await().atMost(5, TimeUnit.SECONDS).until(()
-> activityRule.getActivity().getPlaybackController().getStatus() == PlayerStatus.PLAYING); -> controller.getStatus() == PlayerStatus.PLAYING);
clickThroughSpeeds(); clickThroughSpeeds();
} }
@Test @Test
public void testChangeSpeedPaused() { public void testChangeSpeedPaused() {
onView(isRoot()).perform(waitForView(withId(R.id.butPlay), 1000)); onView(isRoot()).perform(waitForView(withId(R.id.butPlay), 1000));
onView(withId(R.id.butPlay)).perform(click()); controller.playPause();
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() Awaitility.await().atMost(5, TimeUnit.SECONDS).until(()
-> activityRule.getActivity().getPlaybackController().getStatus() == PlayerStatus.PLAYING); -> controller.getStatus() == PlayerStatus.PLAYING);
onView(withId(R.id.butPlay)).perform(click()); controller.playPause();
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() Awaitility.await().atMost(5, TimeUnit.SECONDS).until(()
-> activityRule.getActivity().getPlaybackController().getStatus() == PlayerStatus.PAUSED); -> controller.getStatus() == PlayerStatus.PAUSED);
clickThroughSpeeds(); clickThroughSpeeds();
} }

View File

@ -2,6 +2,8 @@ package de.danoeh.antennapod.activity;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.view.Menu;
/** /**
* Activity that allows for showing the MediaRouter button whenever there's a cast device in the * Activity that allows for showing the MediaRouter button whenever there's a cast device in the
* network. * network.
@ -9,7 +11,7 @@ import androidx.appcompat.app.AppCompatActivity;
public abstract class CastEnabledActivity extends AppCompatActivity { public abstract class CastEnabledActivity extends AppCompatActivity {
public static final String TAG = "CastEnabledActivity"; public static final String TAG = "CastEnabledActivity";
public final void requestCastButton(int showAsAction) { public final void requestCastButton(Menu menu) {
// no-op // no-op
} }
} }

View File

@ -73,31 +73,6 @@
android:label="@string/app_name"> android:label="@string/app_name">
</activity> </activity>
<activity
android:name=".activity.AudioplayerActivity"
android:launchMode="singleTop">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.MainActivity"/>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="file"/>
<data android:mimeType="audio/*"/>
</intent-filter>
</activity>
<activity
android:name=".activity.CastplayerActivity"
android:launchMode="singleTop">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.MainActivity"/>
</activity>
<activity <activity
android:name=".activity.DownloadAuthenticationActivity" android:name=".activity.DownloadAuthenticationActivity"
android:launchMode="singleInstance"/> android:launchMode="singleInstance"/>

View File

@ -0,0 +1,56 @@
package com.google.android.material.bottomsheet;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.view.ViewCompat;
import androidx.viewpager.widget.ViewPager;
import java.lang.ref.WeakReference;
/**
* Override {@link #findScrollingChild(View)} to support {@link ViewPager}'s nested scrolling.
* In order to override package level method and field.
* This class put in the same package path where {@link BottomSheetBehavior} located.
* Source: https://medium.com/@hanru.yeh/funny-solution-that-makes-bottomsheetdialog-support-viewpager-with-nestedscrollingchilds-bfdca72235c3
*/
public class ViewPagerBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {
public ViewPagerBottomSheetBehavior() {
super();
}
public ViewPagerBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
View findScrollingChild(View view) {
if (ViewCompat.isNestedScrollingEnabled(view)) {
return view;
}
if (view instanceof ViewPager) {
ViewPager viewPager = (ViewPager) view;
View currentViewPagerChild = viewPager.getChildAt(viewPager.getCurrentItem());
if (currentViewPagerChild != null) {
return findScrollingChild(currentViewPagerChild);
}
} else if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
for (int i = 0, count = group.getChildCount(); i < count; i++) {
View scrollingChild = findScrollingChild(group.getChildAt(i));
if (scrollingChild != null) {
return scrollingChild;
}
}
}
return null;
}
public void updateScrollingChild() {
final View scrollingChild = findScrollingChild(viewRef.get());
nestedScrollingChildRef = new WeakReference<>(scrollingChild);
}
}

View File

@ -1,136 +0,0 @@
package de.danoeh.antennapod.activity;
import android.content.Intent;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import java.text.DecimalFormat;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Activity for playing audio files.
*/
public class AudioplayerActivity extends MediaplayerInfoActivity {
private static final String TAG = "AudioPlayerActivity";
private static final float EPSILON = 0.001f;
private final AtomicBoolean isSetup = new AtomicBoolean(false);
@Override
protected void onResume() {
super.onResume();
if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
playExternalMedia(getIntent(), MediaType.AUDIO);
} else if (PlaybackService.isCasting()) {
Intent intent = PlaybackService.getPlayerActivityIntent(this);
if (intent.getComponent() != null
&& !intent.getComponent().getClassName().equals(AudioplayerActivity.class.getName())) {
saveCurrentFragment();
finish();
startActivity(intent);
}
}
}
@Override
protected void onReloadNotification(int notificationCode) {
if (notificationCode == PlaybackService.EXTRA_CODE_CAST) {
Log.d(TAG, "ReloadNotification received, switching to Castplayer now");
saveCurrentFragment();
finish();
startActivity(new Intent(this, CastplayerActivity.class));
} else {
super.onReloadNotification(notificationCode);
}
}
@Override
protected void updatePlaybackSpeedButton() {
if (butPlaybackSpeed == null) {
return;
}
if (controller == null) {
butPlaybackSpeed.setVisibility(View.GONE);
txtvPlaybackSpeed.setVisibility(View.GONE);
return;
}
updatePlaybackSpeedButtonText();
butPlaybackSpeed.setAlpha(controller.canSetPlaybackSpeed() ? 1.0f : 0.5f);
butPlaybackSpeed.setVisibility(View.VISIBLE);
txtvPlaybackSpeed.setVisibility(View.VISIBLE);
}
@Override
protected void updatePlaybackSpeedButtonText() {
if (butPlaybackSpeed == null) {
return;
}
if (controller == null) {
butPlaybackSpeed.setVisibility(View.GONE);
txtvPlaybackSpeed.setVisibility(View.GONE);
return;
}
float speed = 1.0f;
if (controller.canSetPlaybackSpeed()) {
speed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(controller.getMedia());
}
String speedStr = new DecimalFormat("0.00").format(speed);
txtvPlaybackSpeed.setText(speedStr);
butPlaybackSpeed.setSpeed(speed);
}
@Override
protected void setupGUI() {
if (isSetup.getAndSet(true)) {
return;
}
super.setupGUI();
if (butPlaybackSpeed != null) {
butPlaybackSpeed.setOnClickListener(v -> {
if (controller == null) {
return;
}
if (controller.canSetPlaybackSpeed()) {
float[] availableSpeeds = UserPreferences.getPlaybackSpeedArray();
float currentSpeed = controller.getCurrentPlaybackSpeedMultiplier();
int newSpeedIndex = 0;
while (newSpeedIndex < availableSpeeds.length
&& availableSpeeds[newSpeedIndex] < currentSpeed + EPSILON) {
newSpeedIndex++;
}
float newSpeed;
if (availableSpeeds.length == 0) {
newSpeed = 1.0f;
} else if (newSpeedIndex == availableSpeeds.length) {
newSpeed = availableSpeeds[0];
} else {
newSpeed = availableSpeeds[newSpeedIndex];
}
PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(newSpeed);
UserPreferences.setPlaybackSpeed(newSpeed);
controller.setPlaybackSpeed(newSpeed);
onPositionObserverUpdate();
} else {
VariableSpeedDialog.showGetPluginDialog(this);
}
});
butPlaybackSpeed.setOnLongClickListener(v -> {
VariableSpeedDialog.showDialog(this);
return true;
});
butPlaybackSpeed.setVisibility(View.VISIBLE);
txtvPlaybackSpeed.setVisibility(View.VISIBLE);
}
}
}

View File

@ -1,80 +0,0 @@
package de.danoeh.antennapod.activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.util.concurrent.atomic.AtomicBoolean;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
/**
* Activity for controlling the remote playback on a Cast device.
*/
public class CastplayerActivity extends MediaplayerInfoActivity {
private static final String TAG = "CastPlayerActivity";
private final AtomicBoolean isSetup = new AtomicBoolean(false);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!PlaybackService.isCasting()) {
Intent intent = PlaybackService.getPlayerActivityIntent(this);
if (!intent.getComponent().getClassName().equals(CastplayerActivity.class.getName())) {
finish();
startActivity(intent);
}
}
}
@Override
protected void onReloadNotification(int notificationCode) {
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) {
Log.d(TAG, "ReloadNotification received, switching to Audioplayer now");
saveCurrentFragment();
finish();
startActivity(new Intent(this, AudioplayerActivity.class));
} else {
super.onReloadNotification(notificationCode);
}
}
@Override
protected void setupGUI() {
if(isSetup.getAndSet(true)) {
return;
}
super.setupGUI();
if (butPlaybackSpeed != null) {
butPlaybackSpeed.setVisibility(View.GONE);
txtvPlaybackSpeed.setVisibility(View.GONE);
}
}
@Override
protected void onResume() {
if (!PlaybackService.isCasting()) {
Intent intent = PlaybackService.getPlayerActivityIntent(this);
if (!intent.getComponent().getClassName().equals(CastplayerActivity.class.getName())) {
saveCurrentFragment();
finish();
startActivity(intent);
}
}
super.onResume();
}
@Override
protected void onBufferStart() {
//sbPosition.setIndeterminate(true);
sbPosition.setEnabled(false);
}
@Override
protected void onBufferEnd() {
//sbPosition.setIndeterminate(false);
sbPosition.setEnabled(true);
}
}

View File

@ -23,6 +23,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.event.MessageEvent; import de.danoeh.antennapod.core.event.MessageEvent;
@ -31,9 +32,9 @@ import de.danoeh.antennapod.core.util.Flavors;
import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.dialog.RatingDialog; import de.danoeh.antennapod.dialog.RatingDialog;
import de.danoeh.antennapod.fragment.AddFeedFragment; import de.danoeh.antennapod.fragment.AddFeedFragment;
import de.danoeh.antennapod.fragment.AudioPlayerFragment;
import de.danoeh.antennapod.fragment.DownloadsFragment; import de.danoeh.antennapod.fragment.DownloadsFragment;
import de.danoeh.antennapod.fragment.EpisodesFragment; import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedItemlistFragment; import de.danoeh.antennapod.fragment.FeedItemlistFragment;
import de.danoeh.antennapod.fragment.NavDrawerFragment; import de.danoeh.antennapod.fragment.NavDrawerFragment;
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment; import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
@ -41,6 +42,7 @@ import de.danoeh.antennapod.fragment.QueueFragment;
import de.danoeh.antennapod.fragment.SubscriptionFragment; import de.danoeh.antennapod.fragment.SubscriptionFragment;
import de.danoeh.antennapod.fragment.TransitionEffect; import de.danoeh.antennapod.fragment.TransitionEffect;
import de.danoeh.antennapod.preferences.PreferenceUpgrader; import de.danoeh.antennapod.preferences.PreferenceUpgrader;
import de.danoeh.antennapod.view.LockableBottomSheetBehavior;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
@ -61,12 +63,14 @@ public class MainActivity extends CastEnabledActivity {
public static final String EXTRA_FRAGMENT_TAG = "fragment_tag"; public static final String EXTRA_FRAGMENT_TAG = "fragment_tag";
public static final String EXTRA_FRAGMENT_ARGS = "fragment_args"; public static final String EXTRA_FRAGMENT_ARGS = "fragment_args";
public static final String EXTRA_FEED_ID = "fragment_feed_id"; public static final String EXTRA_FEED_ID = "fragment_feed_id";
public static final String EXTRA_OPEN_PLAYER = "open_player";
private static final String SAVE_BACKSTACK_COUNT = "backstackCount"; private static final String SAVE_BACKSTACK_COUNT = "backstackCount";
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
private View navDrawer; private View navDrawer;
private ActionBarDrawerToggle drawerToggle; private ActionBarDrawerToggle drawerToggle;
private LockableBottomSheetBehavior sheetBehavior;
private long lastBackButtonPressTime = 0; private long lastBackButtonPressTime = 0;
@NonNull @NonNull
@ -111,15 +115,34 @@ public class MainActivity extends CastEnabledActivity {
} }
} }
} }
ExternalPlayerFragment externalPlayerFragment = new ExternalPlayerFragment();
transaction.replace(R.id.playerFragment, externalPlayerFragment, ExternalPlayerFragment.TAG);
NavDrawerFragment navDrawerFragment = new NavDrawerFragment(); NavDrawerFragment navDrawerFragment = new NavDrawerFragment();
transaction.replace(R.id.navDrawerFragment, navDrawerFragment, NavDrawerFragment.TAG); transaction.replace(R.id.navDrawerFragment, navDrawerFragment, NavDrawerFragment.TAG);
AudioPlayerFragment audioPlayerFragment = new AudioPlayerFragment();
transaction.replace(R.id.audioplayerFragment, audioPlayerFragment, AudioPlayerFragment.TAG);
transaction.commit(); transaction.commit();
checkFirstLaunch(); checkFirstLaunch();
PreferenceUpgrader.checkUpgrades(this); PreferenceUpgrader.checkUpgrades(this);
View bottomSheet = findViewById(R.id.audioplayerFragment);
sheetBehavior = (LockableBottomSheetBehavior) BottomSheetBehavior.from(bottomSheet);
sheetBehavior.setPeekHeight((int) getResources().getDimension(R.dimen.external_player_height));
sheetBehavior.setHideable(false);
sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
@Override
public void onStateChanged(@NonNull View view, int state) {
}
@Override
public void onSlide(@NonNull View view, float slideOffset) {
AudioPlayerFragment audioPlayer =
(AudioPlayerFragment) getSupportFragmentManager().findFragmentByTag(AudioPlayerFragment.TAG);
float condensedSlideOffset = Math.max(0.0f, Math.min(0.1f, slideOffset - 0.5f)) / 0.1f;
audioPlayer.getExternalPlayerHolder().setAlpha(1 - condensedSlideOffset);
audioPlayer.getExternalPlayerHolder().setVisibility(
condensedSlideOffset > 0.99f ? View.GONE : View.VISIBLE);
}
});
} }
@Override @Override
@ -151,6 +174,10 @@ public class MainActivity extends CastEnabledActivity {
return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer); return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer);
} }
public LockableBottomSheetBehavior getBottomSheet() {
return sheetBehavior;
}
public void loadFragment(String tag, Bundle args) { public void loadFragment(String tag, Bundle args) {
Log.d(TAG, "loadFragment(tag: " + tag + ", args: " + args + ")"); Log.d(TAG, "loadFragment(tag: " + tag + ", args: " + args + ")");
Fragment fragment; Fragment fragment;
@ -301,29 +328,6 @@ public class MainActivity extends CastEnabledActivity {
Glide.get(this).clearMemory(); Glide.get(this).clearMemory();
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
boolean retVal = super.onCreateOptionsMenu(menu);
if (Flavors.FLAVOR == Flavors.PLAY) {
switch (NavDrawerFragment.getLastNavFragment(this)) {
case QueueFragment.TAG:
case EpisodesFragment.TAG:
requestCastButton(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return retVal;
case DownloadsFragment.TAG:
case PlaybackHistoryFragment.TAG:
case AddFeedFragment.TAG:
case SubscriptionFragment.TAG:
return retVal;
default:
requestCastButton(MenuItem.SHOW_AS_ACTION_NEVER);
return retVal;
}
} else {
return retVal;
}
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (drawerToggle.onOptionsItemSelected(item)) { if (drawerToggle.onOptionsItemSelected(item)) {
@ -342,6 +346,8 @@ public class MainActivity extends CastEnabledActivity {
public void onBackPressed() { public void onBackPressed() {
if (isDrawerOpen()) { if (isDrawerOpen()) {
drawerLayout.closeDrawer(navDrawer); drawerLayout.closeDrawer(navDrawer);
} else if (sheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else if (getSupportFragmentManager().getBackStackEntryCount() != 0) { } else if (getSupportFragmentManager().getBackStackEntryCount() != 0) {
super.onBackPressed(); super.onBackPressed();
} else { } else {
@ -400,9 +406,11 @@ public class MainActivity extends CastEnabledActivity {
} else if (feedId > 0) { } else if (feedId > 0) {
loadFeedFragmentById(feedId, args); loadFeedFragmentById(feedId, args);
} }
// to avoid handling the intent twice when the configuration changes } else if (intent.hasExtra(EXTRA_OPEN_PLAYER)) {
setIntent(new Intent(MainActivity.this, MainActivity.class)); sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
} }
// to avoid handling the intent twice when the configuration changes
setIntent(new Intent(MainActivity.this, MainActivity.class));
} }
@Override @Override

View File

@ -2,7 +2,6 @@ package de.danoeh.antennapod.activity;
import android.Manifest; import android.Manifest;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -52,6 +51,7 @@ import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.danoeh.antennapod.dialog.PlaybackControlsDialog; import de.danoeh.antennapod.dialog.PlaybackControlsDialog;
import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
import de.danoeh.antennapod.dialog.SleepTimerDialog; import de.danoeh.antennapod.dialog.SleepTimerDialog;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
@ -140,16 +140,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
return butPlay; return butPlay;
} }
@Override
public void postStatusMsg(int msg, boolean showToast) {
MediaplayerActivity.this.postStatusMsg(msg, showToast);
}
@Override
public void clearStatusMsg() {
MediaplayerActivity.this.clearStatusMsg();
}
@Override @Override
public boolean loadMediaInfo() { public boolean loadMediaInfo() {
return MediaplayerActivity.this.loadMediaInfo(); return MediaplayerActivity.this.loadMediaInfo();
@ -198,13 +188,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
onPositionObserverUpdate(); onPositionObserverUpdate();
} }
private static TextView getTxtvFFFromActivity(MediaplayerActivity activity) {
return activity.txtvFF;
}
private static TextView getTxtvRevFromActivity(MediaplayerActivity activity) {
return activity.txtvRev;
}
private void onSetSpeedAbilityChanged() { private void onSetSpeedAbilityChanged() {
Log.d(TAG, "onSetSpeedAbilityChanged()"); Log.d(TAG, "onSetSpeedAbilityChanged()");
updatePlaybackSpeedButton(); updatePlaybackSpeedButton();
@ -258,12 +241,16 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
* Should be used to inform the user that the PlaybackService is currently * Should be used to inform the user that the PlaybackService is currently
* buffering. * buffering.
*/ */
protected abstract void onBufferStart(); protected void onBufferStart() {
}
/** /**
* Should be used to hide the view that was showing the 'buffering'-message. * Should be used to hide the view that was showing the 'buffering'-message.
*/ */
protected abstract void onBufferEnd(); protected void onBufferEnd() {
}
private void onBufferUpdate(float progress) { private void onBufferUpdate(float progress) {
if (sbPosition != null) { if (sbPosition != null) {
@ -311,9 +298,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
if (Flavors.FLAVOR == Flavors.PLAY) { requestCastButton(menu);
requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.mediaplayer, menu); inflater.inflate(R.menu.mediaplayer, menu);
return true; return true;
@ -478,10 +463,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
*/ */
protected abstract void onAwaitingVideoSurface(); protected abstract void onAwaitingVideoSurface();
protected abstract void postStatusMsg(int resId, boolean showToast);
protected abstract void clearStatusMsg();
void onPositionObserverUpdate() { void onPositionObserverUpdate() {
if (controller == null || txtvPosition == null || txtvLength == null) { if (controller == null || txtvPosition == null || txtvLength == null) {
return; return;
@ -543,92 +524,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
// Only meaningful on AudioplayerActivity, where it is overridden. // Only meaningful on AudioplayerActivity, where it is overridden.
} }
/**
* Abstract directions to skip forward or back (rewind) and encapsulates behavior to get or set preference (including update of UI on the skip buttons).
*/
public enum SkipDirection {
SKIP_FORWARD(
UserPreferences::getFastForwardSecs,
MediaplayerActivity::getTxtvFFFromActivity,
UserPreferences::setFastForwardSecs,
R.string.pref_fast_forward),
SKIP_REWIND(UserPreferences::getRewindSecs,
MediaplayerActivity::getTxtvRevFromActivity,
UserPreferences::setRewindSecs,
R.string.pref_rewind);
private final Supplier<Integer> getPrefSecsFn;
private final Function<MediaplayerActivity, TextView> getTextViewFn;
private final Consumer<Integer> setPrefSecsFn;
private final int titleResourceID;
/**
* Constructor for skip direction enum. Stores references to utility functions and resource
* id's that vary dependending on the direction.
*
* @param getPrefSecsFn Handle to function that retrieves current seconds of the skip delta
* @param getTextViewFn Handle to function that gets the TextView which displays the current skip delta value
* @param setPrefSecsFn Handle to function that sets the preference (setting) for the skip delta value (and optionally updates the button label with the current values)
* @param titleResourceID ID of the resource string with the title for a view
*/
SkipDirection(Supplier<Integer> getPrefSecsFn, Function<MediaplayerActivity, TextView> getTextViewFn, Consumer<Integer> setPrefSecsFn, int titleResourceID) {
this.getPrefSecsFn = getPrefSecsFn;
this.getTextViewFn = getTextViewFn;
this.setPrefSecsFn = setPrefSecsFn;
this.titleResourceID = titleResourceID;
}
public int getPrefSkipSeconds() {
return(getPrefSecsFn.get());
}
/**
* Updates preferences for a forward or backward skip depending on the direction of the instance, optionally updating the UI.
*
* @param seconds Number of seconds to set the preference associated with the direction of the instance.
* @param activity MediaplyerActivity that contains textview to update the display of the skip delta setting (or null if nothing to update)
*/
public void setPrefSkipSeconds(int seconds, @Nullable Activity activity) {
setPrefSecsFn.accept(seconds);
if (activity != null && activity instanceof MediaplayerActivity) {
TextView tv = getTextViewFn.apply((MediaplayerActivity)activity);
if (tv != null) tv.setText(String.valueOf(seconds));
}
}
public int getTitleResourceID() {
return titleResourceID;
}
}
public static void showSkipPreference(Activity activity, SkipDirection direction) {
int checked = 0;
int skipSecs = direction.getPrefSkipSeconds();
final int[] values = activity.getResources().getIntArray(R.array.seek_delta_values);
final String[] choices = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (skipSecs == values[i]) {
checked = i;
}
choices[i] = String.valueOf(values[i]) + " " + activity.getString(R.string.time_seconds);
}
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(direction.getTitleResourceID());
builder.setSingleChoiceItems(choices, checked, null);
builder.setNegativeButton(R.string.cancel_label, null);
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
int choice = ((AlertDialog)dialog).getListView().getCheckedItemPosition();
if (choice < 0 || choice >= values.length) {
System.err.printf("Choice in showSkipPreference is out of bounds %d", choice);
} else {
direction.setPrefSkipSeconds(values[choice], activity);
}
});
builder.create().show();
}
void setupGUI() { void setupGUI() {
setContentView(getContentViewResourceId()); setContentView(getContentViewResourceId());
sbPosition = findViewById(R.id.sbPosition); sbPosition = findViewById(R.id.sbPosition);
@ -688,7 +583,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
if (butRev != null) { if (butRev != null) {
butRev.setOnClickListener(v -> onRewind()); butRev.setOnClickListener(v -> onRewind());
butRev.setOnLongClickListener(v -> { butRev.setOnLongClickListener(v -> {
showSkipPreference(MediaplayerActivity.this, SkipDirection.SKIP_REWIND); SkipPreferenceDialog.showSkipPreference(MediaplayerActivity.this,
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev);
return true; return true;
}); });
} }
@ -698,7 +594,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
if (butFF != null) { if (butFF != null) {
butFF.setOnClickListener(v -> onFastForward()); butFF.setOnClickListener(v -> onFastForward());
butFF.setOnLongClickListener(v -> { butFF.setOnLongClickListener(v -> {
showSkipPreference(MediaplayerActivity.this, SkipDirection.SKIP_FORWARD); SkipPreferenceDialog.showSkipPreference(MediaplayerActivity.this,
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF);
return false; return false;
}); });
} }

View File

@ -1,282 +0,0 @@
package de.danoeh.antennapod.activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.snackbar.Snackbar;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.event.MessageEvent;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.fragment.ChaptersFragment;
import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
import de.danoeh.antennapod.fragment.NavDrawerFragment;
import de.danoeh.antennapod.view.PagerIndicatorView;
import de.danoeh.antennapod.view.PlaybackSpeedIndicatorView;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.util.List;
/**
* Activity for playing files that do not require a video surface.
*/
public abstract class MediaplayerInfoActivity extends MediaplayerActivity {
private static final String TAG = "MediaplayerInfoActivity";
private static final int POS_COVER = 0;
private static final int POS_DESCR = 1;
private static final int POS_CHAPTERS = 2;
private static final int NUM_CONTENT_FRAGMENTS = 3;
private static final String PREFS = "AudioPlayerActivityPreferences";
private static final String PREF_KEY_SELECTED_FRAGMENT_POSITION = "selectedFragmentPosition";
PlaybackSpeedIndicatorView butPlaybackSpeed;
TextView txtvPlaybackSpeed;
private DrawerLayout drawerLayout;
private View navDrawer;
private ActionBarDrawerToggle drawerToggle;
private ViewPager pager;
private PagerIndicatorView pageIndicator;
private MediaplayerInfoPagerAdapter pagerAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportPostponeEnterTransition();
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop()");
saveCurrentFragment();
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy()");
super.onDestroy();
// don't risk creating memory leaks
drawerLayout = null;
navDrawer = null;
drawerToggle = null;
pager = null;
pagerAdapter = null;
}
@Override
protected void chooseTheme() {
setTheme(UserPreferences.getNoTitleTheme());
}
void saveCurrentFragment() {
if (pager == null) {
return;
}
Log.d(TAG, "Saving preferences");
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
prefs.edit()
.putInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, pager.getCurrentItem())
.apply();
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (drawerToggle != null) {
drawerToggle.onConfigurationChanged(newConfig);
}
}
private void loadLastFragment() {
Log.d(TAG, "Restoring instance state");
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
int lastPosition = prefs.getInt(PREF_KEY_SELECTED_FRAGMENT_POSITION, -1);
pager.setCurrentItem(lastPosition);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
protected void onAwaitingVideoSurface() {
Log.d(TAG, "onAwaitingVideoSurface was called in audio player -> switching to video player");
startActivity(new Intent(this, VideoplayerActivity.class));
}
@Override
protected void postStatusMsg(int resId, boolean showToast) {
if (resId == R.string.player_preparing_msg
|| resId == R.string.player_seeking_msg
|| resId == R.string.player_buffering_msg) {
// TODO Show progress bar here
}
if (showToast) {
Toast.makeText(this, resId, Toast.LENGTH_SHORT).show();
}
}
@Override
protected void clearStatusMsg() {
// TODO Hide progress bar here
}
@Override
protected void setupGUI() {
super.setupGUI();
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("");
drawerLayout = findViewById(R.id.drawer_layout);
navDrawer = findViewById(R.id.navDrawerFragment);
butPlaybackSpeed = findViewById(R.id.butPlaybackSpeed);
txtvPlaybackSpeed = findViewById(R.id.txtvPlaybackSpeed);
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.drawer_open, R.string.drawer_close);
drawerToggle.setDrawerIndicatorEnabled(false);
drawerLayout.addDrawerListener(drawerToggle);
drawerToggle.syncState();
getSupportFragmentManager().beginTransaction()
.replace(R.id.navDrawerFragment, new NavDrawerFragment(), NavDrawerFragment.TAG)
.commit();
pager = findViewById(R.id.pager);
pager.setOffscreenPageLimit(3);
pagerAdapter = new MediaplayerInfoPagerAdapter(getSupportFragmentManager());
pager.setAdapter(pagerAdapter);
pageIndicator = findViewById(R.id.page_indicator);
pageIndicator.setViewPager(pager);
pageIndicator.setOnClickListener(v
-> pager.setCurrentItem((pager.getCurrentItem() + 1) % pager.getChildCount()));
loadLastFragment();
pager.onSaveInstanceState();
pager.post(this::supportStartPostponedEnterTransition);
}
@Override
boolean loadMediaInfo() {
if (controller != null && controller.getMedia() != null) {
List<Chapter> chapters = controller.getMedia().getChapters();
boolean hasChapters = chapters != null && !chapters.isEmpty();
pageIndicator.setDisabledPage(hasChapters ? -1 : 2);
}
return super.loadMediaInfo();
}
@Override
protected void onReloadNotification(int notificationCode) {
if (notificationCode == PlaybackService.EXTRA_CODE_VIDEO) {
Log.d(TAG, "ReloadNotification received, switching to Videoplayer now");
finish();
startActivity(new Intent(this, VideoplayerActivity.class));
}
}
@Override
protected void onBufferStart() {
postStatusMsg(R.string.player_buffering_msg, false);
}
@Override
protected void onBufferEnd() {
clearStatusMsg();
}
public PlaybackController getPlaybackController() {
return controller;
}
public boolean isDrawerOpen() {
return drawerLayout != null && navDrawer != null && drawerLayout.isDrawerOpen(navDrawer);
}
@Override
protected int getContentViewResourceId() {
return R.layout.mediaplayerinfo_activity;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return (drawerToggle != null && drawerToggle.onOptionsItemSelected(item)) || super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
if (isDrawerOpen()) {
drawerLayout.closeDrawer(navDrawer);
} else if (pager == null || pager.getCurrentItem() == 0) {
// If the user is currently looking at the first step, allow the system to handle the
// Back button. This calls finish() on this activity and pops the back stack.
super.onBackPressed();
} else {
// Otherwise, select the previous step.
pager.setCurrentItem(pager.getCurrentItem() - 1);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(MessageEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
View parentLayout = findViewById(R.id.drawer_layout);
Snackbar snackbar = Snackbar.make(parentLayout, event.message, Snackbar.LENGTH_SHORT);
if (event.action != null) {
snackbar.setAction(getString(R.string.undo), v -> event.action.run());
}
snackbar.show();
}
private static class MediaplayerInfoPagerAdapter extends FragmentStatePagerAdapter {
private static final String TAG = "MPInfoPagerAdapter";
public MediaplayerInfoPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem(" + position + ")");
switch (position) {
case POS_COVER:
return new CoverFragment();
case POS_DESCR:
return new ItemDescriptionFragment();
case POS_CHAPTERS:
return new ChaptersFragment();
default:
return null;
}
}
@Override
public int getCount() {
return NUM_CONTENT_FRAGMENTS;
}
}
}

View File

@ -171,20 +171,6 @@ public class VideoplayerActivity extends MediaplayerActivity {
} }
} }
@Override
protected void postStatusMsg(int resId, boolean showToast) {
if (resId == R.string.player_preparing_msg) {
progressIndicator.setVisibility(View.VISIBLE);
} else {
progressIndicator.setVisibility(View.INVISIBLE);
}
}
@Override
protected void clearStatusMsg() {
progressIndicator.setVisibility(View.INVISIBLE);
}
private final View.OnTouchListener onVideoviewTouched = (v, event) -> { private final View.OnTouchListener onVideoviewTouched = (v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) { if (event.getAction() == MotionEvent.ACTION_DOWN) {
if (PictureInPictureUtil.isInPictureInPictureMode(this)) { if (PictureInPictureUtil.isInPictureInPictureMode(this)) {
@ -292,16 +278,11 @@ public class VideoplayerActivity extends MediaplayerActivity {
} }
return; return;
} }
if (notificationCode == PlaybackService.EXTRA_CODE_AUDIO) { if (notificationCode == PlaybackService.EXTRA_CODE_CAST) {
Log.d(TAG, "ReloadNotification received, switching to Audioplayer now");
destroyingDueToReload = true;
finish();
startActivity(new Intent(this, AudioplayerActivity.class));
} else if (notificationCode == PlaybackService.EXTRA_CODE_CAST) {
Log.d(TAG, "ReloadNotification received, switching to Castplayer now"); Log.d(TAG, "ReloadNotification received, switching to Castplayer now");
destroyingDueToReload = true; destroyingDueToReload = true;
finish(); finish();
startActivity(new Intent(this, CastplayerActivity.class)); startActivity(new Intent(this, MainActivity.class).putExtra(MainActivity.EXTRA_OPEN_PLAYER, true));
} }
} }

View File

@ -5,37 +5,34 @@ import android.text.TextUtils;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.FitCenter; import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage; import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.core.util.ThemeUtils;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
public class ChaptersListAdapter extends ArrayAdapter<Chapter> { public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapter.ChapterHolder> {
private static final String TAG = "ChapterListAdapter";
private Playable media; private Playable media;
private final Callback callback; private final Callback callback;
private final Context context;
private int currentChapterIndex = -1; private int currentChapterIndex = -1;
private boolean hasImages = false; private boolean hasImages = false;
public ChaptersListAdapter(Context context, int textViewResourceId, Callback callback) { public ChaptersListAdapter(Context context, Callback callback) {
super(context, textViewResourceId);
this.callback = callback; this.callback = callback;
this.context = context;
} }
public void setMedia(Playable media) { public void setMedia(Playable media) {
@ -51,34 +48,10 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
notifyDataSetChanged(); notifyDataSetChanged();
} }
@NonNull
@Override @Override
public View getView(final int position, View convertView, @NonNull ViewGroup parent) { public void onBindViewHolder(@NonNull ChapterHolder holder, int position) {
Holder holder;
Chapter sc = getItem(position); Chapter sc = getItem(position);
// Inflate Layout
if (convertView == null) {
holder = new Holder();
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.simplechapter_item, parent, false);
holder.view = convertView;
holder.title = convertView.findViewById(R.id.txtvTitle);
holder.start = convertView.findViewById(R.id.txtvStart);
holder.link = convertView.findViewById(R.id.txtvLink);
holder.image = convertView.findViewById(R.id.imgvCover);
holder.duration = convertView.findViewById(R.id.txtvDuration);
holder.secondaryActionButton = convertView.findViewById(R.id.secondaryActionButton);
holder.secondaryActionIcon = convertView.findViewById(R.id.secondaryActionIcon);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.title.setText(sc.getTitle()); holder.title.setText(sc.getTitle());
holder.start.setText(Converter.getDurationStringLong((int) sc holder.start.setText(Converter.getDurationStringLong((int) sc
.getStart())); .getStart()));
@ -89,7 +62,7 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
} else { } else {
duration = media.getDuration() - sc.getStart(); duration = media.getDuration() - sc.getStart();
} }
holder.duration.setText(getContext().getString(R.string.chapter_duration, holder.duration.setText(context.getString(R.string.chapter_duration,
Converter.getDurationStringLong((int) duration))); Converter.getDurationStringLong((int) duration)));
if (sc.getLink() == null) { if (sc.getLink() == null) {
@ -97,9 +70,9 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
} else { } else {
holder.link.setVisibility(View.VISIBLE); holder.link.setVisibility(View.VISIBLE);
holder.link.setText(sc.getLink()); holder.link.setText(sc.getLink());
holder.link.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), sc.getLink())); holder.link.setOnClickListener(v -> IntentUtils.openInBrowser(context, sc.getLink()));
} }
holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(getContext(), R.attr.av_play)); holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.av_play));
holder.secondaryActionButton.setOnClickListener(v -> { holder.secondaryActionButton.setOnClickListener(v -> {
if (callback != null) { if (callback != null) {
callback.onPlayChapterButtonClicked(position); callback.onPlayChapterButtonClicked(position);
@ -107,46 +80,40 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
}); });
if (position == currentChapterIndex) { if (position == currentChapterIndex) {
int playingBackGroundColor = ThemeUtils.getColorFromAttr(getContext(), R.attr.currently_playing_background); int playingBackGroundColor = ThemeUtils.getColorFromAttr(context, R.attr.currently_playing_background);
holder.view.setBackgroundColor(playingBackGroundColor); holder.itemView.setBackgroundColor(playingBackGroundColor);
} else { } else {
holder.view.setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent)); holder.itemView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent));
} }
if (hasImages) { if (hasImages) {
holder.image.setVisibility(View.VISIBLE); holder.image.setVisibility(View.VISIBLE);
if (TextUtils.isEmpty(sc.getImageUrl())) { if (TextUtils.isEmpty(sc.getImageUrl())) {
Glide.with(getContext()).clear(holder.image); Glide.with(context).clear(holder.image);
} else { } else {
Glide.with(getContext()) Glide.with(context)
.load(EmbeddedChapterImage.getModelFor(media, position)) .load(EmbeddedChapterImage.getModelFor(media, position))
.apply(new RequestOptions() .apply(new RequestOptions()
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.dontAnimate() .dontAnimate()
.transforms(new FitCenter(), new RoundedCorners((int) .transforms(new FitCenter(), new RoundedCorners((int)
(4 * getContext().getResources().getDisplayMetrics().density)))) (4 * context.getResources().getDisplayMetrics().density))))
.into(holder.image); .into(holder.image);
} }
} else { } else {
holder.image.setVisibility(View.GONE); holder.image.setVisibility(View.GONE);
} }
return convertView;
} }
static class Holder { @NonNull
View view; @Override
TextView title; public ChapterHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
TextView start; LayoutInflater inflater = LayoutInflater.from(context);
TextView link; return new ChapterHolder(inflater.inflate(R.layout.simplechapter_item, parent, false));
TextView duration;
ImageView image;
View secondaryActionButton;
ImageView secondaryActionIcon;
} }
@Override @Override
public int getCount() { public int getItemCount() {
if (media == null || media.getChapters() == null) { if (media == null || media.getChapters() == null) {
return 0; return 0;
} }
@ -160,6 +127,27 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
return counter; return counter;
} }
static class ChapterHolder extends RecyclerView.ViewHolder {
final TextView title;
final TextView start;
final TextView link;
final TextView duration;
final ImageView image;
final View secondaryActionButton;
final ImageView secondaryActionIcon;
public ChapterHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(R.id.txtvTitle);
start = itemView.findViewById(R.id.txtvStart);
link = itemView.findViewById(R.id.txtvLink);
image = itemView.findViewById(R.id.imgvCover);
duration = itemView.findViewById(R.id.txtvDuration);
secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton);
secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon);
}
}
public void notifyChapterChanged(int newChapterIndex) { public void notifyChapterChanged(int newChapterIndex) {
currentChapterIndex = newChapterIndex; currentChapterIndex = newChapterIndex;
notifyDataSetChanged(); notifyDataSetChanged();
@ -169,7 +157,6 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
return media.getDuration() > 0 && media.getDuration() < c.getStart(); return media.getDuration() > 0 && media.getDuration() < c.getStart();
} }
@Override
public Chapter getItem(int position) { public Chapter getItem(int position) {
int i = 0; int i = 0;
for (Chapter chapter : media.getChapters()) { for (Chapter chapter : media.getChapters()) {
@ -181,7 +168,7 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
} }
} }
} }
return super.getItem(position); return null;
} }
public interface Callback { public interface Callback {

View File

@ -4,27 +4,22 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AudioplayerActivity; import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.CastplayerActivity;
import de.danoeh.antennapod.activity.VideoplayerActivity; import de.danoeh.antennapod.activity.VideoplayerActivity;
import de.danoeh.antennapod.core.PlaybackServiceCallbacks; import de.danoeh.antennapod.core.PlaybackServiceCallbacks;
import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.feed.MediaType;
public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks { public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks {
@Override @Override
public Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback) { public Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback) {
if (remotePlayback) { if (mediaType == MediaType.AUDIO || remotePlayback) {
return new Intent(context, CastplayerActivity.class); return new Intent(context, MainActivity.class).putExtra(MainActivity.EXTRA_OPEN_PLAYER, true);
} } else {
if (mediaType == MediaType.VIDEO) {
Intent i = new Intent(context, VideoplayerActivity.class); Intent i = new Intent(context, VideoplayerActivity.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
i.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); i.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
} }
return i; return i;
} else {
return new Intent(context, AudioplayerActivity.class);
} }
} }

View File

@ -313,7 +313,7 @@ public class EpisodesApplyActionFragment extends Fragment {
} }
} }
if (resId != 0) { if (resId != 0) {
Snackbar.make(getActivity().findViewById(R.id.content), resId, Snackbar.LENGTH_SHORT) Snackbar.make(getActivity().findViewById(android.R.id.content), resId, Snackbar.LENGTH_SHORT)
.show(); .show();
return true; return true;
} else { } else {
@ -469,7 +469,7 @@ public class EpisodesApplyActionFragment extends Fragment {
private void close(@PluralsRes int msgId, int numItems) { private void close(@PluralsRes int msgId, int numItems) {
if (numItems > 0) { if (numItems > 0) {
Snackbar.make(getActivity().findViewById(R.id.content), Snackbar.make(getActivity().findViewById(android.R.id.content),
getResources().getQuantityString(msgId, numItems, numItems), getResources().getQuantityString(msgId, numItems, numItems),
Snackbar.LENGTH_LONG Snackbar.LENGTH_LONG
) )

View File

@ -0,0 +1,58 @@
package de.danoeh.antennapod.dialog;
import android.content.Context;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
/**
* Shows the dialog that allows setting the skip time.
*/
public class SkipPreferenceDialog {
public static void showSkipPreference(Context context, SkipDirection direction, TextView textView) {
int checked = 0;
int skipSecs;
if (direction == SkipDirection.SKIP_FORWARD) {
skipSecs = UserPreferences.getFastForwardSecs();
} else {
skipSecs = UserPreferences.getRewindSecs();
}
final int[] values = context.getResources().getIntArray(R.array.seek_delta_values);
final String[] choices = new String[values.length];
for (int i = 0; i < values.length; i++) {
if (skipSecs == values[i]) {
checked = i;
}
choices[i] = values[i] + " " + context.getString(R.string.time_seconds);
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(direction == SkipDirection.SKIP_FORWARD ? R.string.pref_fast_forward : R.string.pref_rewind);
builder.setSingleChoiceItems(choices, checked, null);
builder.setNegativeButton(R.string.cancel_label, null);
builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
int choice = ((AlertDialog) dialog).getListView().getCheckedItemPosition();
if (choice < 0 || choice >= values.length) {
System.err.printf("Choice in showSkipPreference is out of bounds %d", choice);
} else {
int seconds = values[choice];
if (direction == SkipDirection.SKIP_FORWARD) {
UserPreferences.setFastForwardSecs(seconds);
} else {
UserPreferences.setRewindSecs(seconds);
}
if (textView != null) {
textView.setText(String.valueOf(seconds));
}
}
});
builder.create().show();
}
public enum SkipDirection {
SKIP_FORWARD, SKIP_REWIND
}
}

View File

@ -0,0 +1,505 @@
package de.danoeh.antennapod.fragment;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.CastEnabledActivity;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.event.FavoritesEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.TimeSpeedConverter;
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.dialog.PlaybackControlsDialog;
import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
import de.danoeh.antennapod.dialog.SleepTimerDialog;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
import de.danoeh.antennapod.view.PagerIndicatorView;
import de.danoeh.antennapod.view.PlaybackSpeedIndicatorView;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.text.DecimalFormat;
import java.util.List;
/**
* Shows the audio player.
*/
public class AudioPlayerFragment extends Fragment implements
SeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
public static final String TAG = "AudioPlayerFragment";
private static final int POS_COVER = 0;
private static final int POS_DESCR = 1;
private static final int POS_CHAPTERS = 2;
private static final int NUM_CONTENT_FRAGMENTS = 3;
private static final String PREFS = "AudioPlayerFragmentPreferences";
private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
private static final float EPSILON = 0.001f;
PlaybackSpeedIndicatorView butPlaybackSpeed;
TextView txtvPlaybackSpeed;
private ViewPager pager;
private PagerIndicatorView pageIndicator;
private TextView txtvPosition;
private TextView txtvLength;
private SeekBar sbPosition;
private ImageButton butRev;
private TextView txtvRev;
private ImageButton butPlay;
private ImageButton butFF;
private TextView txtvFF;
private ImageButton butSkip;
private Toolbar toolbar;
private ProgressBar progressIndicator;
private PlaybackController controller;
private boolean showTimeLeft;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View root = inflater.inflate(R.layout.audioplayer_fragment, container, false);
toolbar = root.findViewById(R.id.toolbar);
toolbar.setTitle("");
toolbar.setNavigationOnClickListener(v ->
((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED));
toolbar.setOnMenuItemClickListener(this);
setupOptionsMenu();
ExternalPlayerFragment externalPlayerFragment = new ExternalPlayerFragment();
getFragmentManager().beginTransaction()
.replace(R.id.playerFragment, externalPlayerFragment, ExternalPlayerFragment.TAG)
.commit();
butPlaybackSpeed = root.findViewById(R.id.butPlaybackSpeed);
txtvPlaybackSpeed = root.findViewById(R.id.txtvPlaybackSpeed);
sbPosition = root.findViewById(R.id.sbPosition);
txtvPosition = root.findViewById(R.id.txtvPosition);
txtvLength = root.findViewById(R.id.txtvLength);
butRev = root.findViewById(R.id.butRev);
txtvRev = root.findViewById(R.id.txtvRev);
butPlay = root.findViewById(R.id.butPlay);
butFF = root.findViewById(R.id.butFF);
txtvFF = root.findViewById(R.id.txtvFF);
butSkip = root.findViewById(R.id.butSkip);
progressIndicator = root.findViewById(R.id.progLoading);
setupLengthTextView();
setupControlButtons();
setupPlaybackSpeedButton();
txtvRev.setText(String.valueOf(UserPreferences.getRewindSecs()));
txtvFF.setText(String.valueOf(UserPreferences.getFastForwardSecs()));
sbPosition.setOnSeekBarChangeListener(this);
pager = root.findViewById(R.id.pager);
AudioPlayerPagerAdapter pagerAdapter = new AudioPlayerPagerAdapter(getFragmentManager());
pager.setAdapter(pagerAdapter);
// Required for getChildAt(int) in ViewPagerBottomSheetBehavior to return the correct page
pager.setOffscreenPageLimit(NUM_CONTENT_FRAGMENTS);
pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
pager.post(() -> ((MainActivity) getActivity()).getBottomSheet().updateScrollingChild());
}
});
pageIndicator = root.findViewById(R.id.page_indicator);
pageIndicator.setViewPager(pager);
pageIndicator.setOnClickListener(v ->
pager.setCurrentItem((pager.getCurrentItem() + 1) % pager.getChildCount()));
return root;
}
public View getExternalPlayerHolder() {
return getView().findViewById(R.id.playerFragment);
}
private void setupControlButtons() {
butRev.setOnClickListener(v -> {
if (controller != null) {
int curr = controller.getPosition();
controller.seekTo(curr - UserPreferences.getRewindSecs() * 1000);
}
});
butRev.setOnLongClickListener(v -> {
SkipPreferenceDialog.showSkipPreference(getContext(),
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev);
return true;
});
butPlay.setOnClickListener(v -> {
if (controller != null) {
controller.init();
controller.playPause();
}
});
butFF.setOnClickListener(v -> {
if (controller != null) {
int curr = controller.getPosition();
controller.seekTo(curr + UserPreferences.getFastForwardSecs() * 1000);
}
});
butFF.setOnLongClickListener(v -> {
SkipPreferenceDialog.showSkipPreference(getContext(),
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF);
return false;
});
butSkip.setOnClickListener(v ->
IntentUtils.sendLocalBroadcast(getActivity(), PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
}
private void setupLengthTextView() {
SharedPreferences prefs = getContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
txtvLength.setOnClickListener(v -> {
if (controller == null) {
return;
}
showTimeLeft = !showTimeLeft;
prefs.edit().putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft).apply();
updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
});
}
private void setupPlaybackSpeedButton() {
butPlaybackSpeed.setOnClickListener(v -> {
if (controller == null) {
return;
}
if (!controller.canSetPlaybackSpeed()) {
VariableSpeedDialog.showGetPluginDialog(getContext());
return;
}
float[] availableSpeeds = UserPreferences.getPlaybackSpeedArray();
float currentSpeed = controller.getCurrentPlaybackSpeedMultiplier();
int newSpeedIndex = 0;
while (newSpeedIndex < availableSpeeds.length && availableSpeeds[newSpeedIndex] < currentSpeed + EPSILON) {
newSpeedIndex++;
}
float newSpeed;
if (availableSpeeds.length == 0) {
newSpeed = 1.0f;
} else if (newSpeedIndex == availableSpeeds.length) {
newSpeed = availableSpeeds[0];
} else {
newSpeed = availableSpeeds[newSpeedIndex];
}
PlaybackPreferences.setCurrentlyPlayingTemporaryPlaybackSpeed(newSpeed);
UserPreferences.setPlaybackSpeed(newSpeed);
controller.setPlaybackSpeed(newSpeed);
updateUi();
});
butPlaybackSpeed.setOnLongClickListener(v -> {
VariableSpeedDialog.showDialog(getContext());
return true;
});
butPlaybackSpeed.setVisibility(View.VISIBLE);
txtvPlaybackSpeed.setVisibility(View.VISIBLE);
}
protected void updatePlaybackSpeedButton() {
if (butPlaybackSpeed == null || controller == null) {
return;
}
float speed = 1.0f;
if (controller.canSetPlaybackSpeed()) {
speed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(controller.getMedia());
}
String speedStr = new DecimalFormat("0.00").format(speed);
txtvPlaybackSpeed.setText(speedStr);
butPlaybackSpeed.setSpeed(speed);
butPlaybackSpeed.setAlpha(controller.canSetPlaybackSpeed() ? 1.0f : 0.5f);
butPlaybackSpeed.setVisibility(View.VISIBLE);
txtvPlaybackSpeed.setVisibility(View.VISIBLE);
}
private PlaybackController newPlaybackController() {
return new PlaybackController(getActivity(), false) {
@Override
public void setupGUI() {
updateUi();
}
@Override
public void onBufferStart() {
progressIndicator.setVisibility(View.VISIBLE);
}
@Override
public void onBufferEnd() {
progressIndicator.setVisibility(View.GONE);
}
@Override
public void onBufferUpdate(float progress) {
sbPosition.setSecondaryProgress((int) (progress * sbPosition.getMax()));
}
@Override
public void handleError(int code) {
final AlertDialog.Builder errorDialog = new AlertDialog.Builder(getContext());
errorDialog.setTitle(R.string.error_label);
errorDialog.setMessage(MediaPlayerError.getErrorString(getContext(), code));
errorDialog.setNeutralButton(android.R.string.ok,
(dialog, which) -> {
dialog.dismiss();
((MainActivity) getActivity()).getBottomSheet()
.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
);
errorDialog.create().show();
}
@Override
public void onSleepTimerUpdate() {
setupOptionsMenu();
}
@Override
public ImageButton getPlayButton() {
return butPlay;
}
@Override
public boolean loadMediaInfo() {
updateUi();
return true;
}
@Override
public void onShutdownNotification() {
((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED);
}
@Override
public void onPlaybackEnd() {
((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED);
}
@Override
public void onPlaybackSpeedChange() {
updatePlaybackSpeedButton();
}
@Override
public void onSetSpeedAbilityChanged() {
updatePlaybackSpeedButton();
}
};
}
private void updateUi() {
if (controller == null) {
return;
}
updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
updatePlaybackSpeedButton();
setupOptionsMenu();
if (controller.getMedia() != null) {
List<Chapter> chapters = controller.getMedia().getChapters();
boolean hasChapters = chapters != null && !chapters.isEmpty();
pageIndicator.setDisabledPage(hasChapters ? -1 : 2);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onStart() {
super.onStart();
controller = newPlaybackController();
controller.init();
updateUi();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
super.onStop();
controller.release();
controller = null;
EventBus.getDefault().unregister(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void updatePosition(PlaybackPositionEvent event) {
if (controller == null || txtvPosition == null || txtvLength == null || sbPosition == null) {
return;
}
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
int currentPosition = converter.convert(event.getPosition());
int duration = converter.convert(event.getDuration());
int remainingTime = converter.convert(event.getDuration() - event.getPosition());
Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
if (currentPosition == PlaybackService.INVALID_TIME || duration == PlaybackService.INVALID_TIME) {
Log.w(TAG, "Could not react to position observer update because of invalid time");
return;
}
txtvPosition.setText(Converter.getDurationStringLong(currentPosition));
if (showTimeLeft) {
txtvLength.setText("-" + Converter.getDurationStringLong(remainingTime));
} else {
txtvLength.setText(Converter.getDurationStringLong(duration));
}
float progress = ((float) event.getPosition()) / event.getDuration();
sbPosition.setProgress((int) (progress * sbPosition.getMax()));
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void favoritesChanged(FavoritesEvent event) {
setupOptionsMenu();
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (controller == null || txtvLength == null) {
return;
}
if (fromUser) {
float prog = progress / ((float) seekBar.getMax());
int duration = controller.getDuration();
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
int position = converter.convert((int) (prog * duration));
txtvPosition.setText(Converter.getDurationStringLong(position));
if (showTimeLeft && prog != 0) {
int timeLeft = converter.convert(duration - (int) (prog * duration));
String length = "-" + Converter.getDurationStringLong(timeLeft);
txtvLength.setText(length);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// interrupt position Observer, restart later
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (controller != null) {
float prog = seekBar.getProgress() / ((float) seekBar.getMax());
controller.seekTo((int) (prog * controller.getDuration()));
}
}
public void setupOptionsMenu() {
if (toolbar.getMenu().size() == 0) {
toolbar.inflateMenu(R.menu.mediaplayer);
}
if (controller == null) {
return;
}
Playable media = controller.getMedia();
boolean isFeedMedia = media instanceof FeedMedia;
toolbar.getMenu().findItem(R.id.open_feed_item).setVisible(isFeedMedia);
if (isFeedMedia) {
FeedItemMenuHandler.onPrepareMenu(toolbar.getMenu(), ((FeedMedia) media).getItem());
}
toolbar.getMenu().findItem(R.id.set_sleeptimer_item).setVisible(!controller.sleepTimerActive());
toolbar.getMenu().findItem(R.id.disable_sleeptimer_item).setVisible(controller.sleepTimerActive());
((CastEnabledActivity) getActivity()).requestCastButton(toolbar.getMenu());
}
@Override
public boolean onMenuItemClick(MenuItem item) {
if (controller == null) {
return false;
}
Playable media = controller.getMedia();
if (media == null) {
return false;
}
final @Nullable FeedItem feedItem = (media instanceof FeedMedia) ? ((FeedMedia) media).getItem() : null;
if (feedItem != null && FeedItemMenuHandler.onMenuItemClicked(this, item.getItemId(), feedItem)) {
return true;
}
switch (item.getItemId()) {
case R.id.disable_sleeptimer_item: // Fall-through
case R.id.set_sleeptimer_item:
new SleepTimerDialog().show(getFragmentManager(), "SleepTimerDialog");
return true;
case R.id.audio_controls:
PlaybackControlsDialog dialog = PlaybackControlsDialog.newInstance(false);
dialog.show(getFragmentManager(), "playback_controls");
return true;
case R.id.open_feed_item:
if (feedItem != null) {
Intent intent = MainActivity.getIntentToOpenFeed(getContext(), feedItem.getFeedId());
startActivity(intent);
}
return true;
}
return false;
}
private static class AudioPlayerPagerAdapter extends FragmentPagerAdapter {
private static final String TAG = "AudioPlayerPagerAdapter";
public AudioPlayerPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem(" + position + ")");
switch (position) {
case POS_COVER:
return new CoverFragment();
case POS_DESCR:
return new ItemDescriptionFragment();
case POS_CHAPTERS:
return new ChaptersFragment();
default:
return null;
}
}
@Override
public int getCount() {
return NUM_CONTENT_FRAGMENTS;
}
}
}

View File

@ -1,24 +1,24 @@
package de.danoeh.antennapod.fragment; package de.danoeh.antennapod.fragment;
import android.os.Bundle; import android.os.Bundle;
import androidx.fragment.app.ListFragment;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.ListView; import android.view.ViewGroup;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.util.ChapterUtils; import androidx.annotation.Nullable;
import java.util.List; import androidx.fragment.app.Fragment;
import java.util.ListIterator; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChaptersListAdapter; import de.danoeh.antennapod.adapter.ChaptersListAdapter;
import de.danoeh.antennapod.adapter.QueueRecyclerAdapter;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.view.EmptyViewHandler; import de.danoeh.antennapod.view.EmptyViewHandler;
import io.reactivex.Maybe; import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
@ -28,38 +28,43 @@ import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode; import org.greenrobot.eventbus.ThreadMode;
public class ChaptersFragment extends ListFragment { public class ChaptersFragment extends Fragment {
private static final String TAG = "ChaptersFragment"; private static final String TAG = "ChaptersFragment";
private ChaptersListAdapter adapter; private ChaptersListAdapter adapter;
private PlaybackController controller; private PlaybackController controller;
private Disposable disposable; private Disposable disposable;
private int focusedChapter = -1; private int focusedChapter = -1;
private Playable media; private Playable media;
private LinearLayoutManager layoutManager;
@Nullable
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); View root = inflater.inflate(R.layout.simple_list_fragment, container, false);
// add padding root.findViewById(R.id.toolbar).setVisibility(View.GONE);
final ListView lv = getListView(); RecyclerView recyclerView = root.findViewById(R.id.recyclerView);
lv.setClipToPadding(false); layoutManager = new LinearLayoutManager(getActivity());
final int vertPadding = getResources().getDimensionPixelSize(R.dimen.list_vertical_padding); recyclerView.setLayoutManager(layoutManager);
lv.setPadding(0, vertPadding, 0, vertPadding); recyclerView.setHasFixedSize(true);
recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
adapter = new ChaptersListAdapter(getActivity(), pos -> {
if (controller.getStatus() != PlayerStatus.PLAYING) {
controller.playPause();
}
Chapter chapter = adapter.getItem(pos);
controller.seekToChapter(chapter);
updateChapterSelection(pos);
});
recyclerView.setAdapter(adapter);
EmptyViewHandler emptyView = new EmptyViewHandler(getContext()); EmptyViewHandler emptyView = new EmptyViewHandler(getContext());
emptyView.attachToListView(lv); emptyView.attachToRecyclerView(recyclerView);
emptyView.setIcon(R.attr.ic_bookmark); emptyView.setIcon(R.attr.ic_bookmark);
emptyView.setTitle(R.string.no_chapters_head_label); emptyView.setTitle(R.string.no_chapters_head_label);
emptyView.setMessage(R.string.no_chapters_label); emptyView.setMessage(R.string.no_chapters_label);
adapter = new ChaptersListAdapter(getActivity(), 0, pos -> { return root;
if (controller.getStatus() != PlayerStatus.PLAYING) {
controller.playPause();
}
Chapter chapter = (Chapter) getListAdapter().getItem(pos);
controller.seekToChapter(chapter);
updateChapterSelection(pos);
});
setListAdapter(adapter);
} }
@Override @Override
@ -136,7 +141,6 @@ public class ChaptersFragment extends ListFragment {
return; return;
} }
adapter.setMedia(media); adapter.setMedia(media);
adapter.notifyDataSetChanged();
int positionOfCurrentChapter = getCurrentChapter(media); int positionOfCurrentChapter = getCurrentChapter(media);
updateChapterSelection(positionOfCurrentChapter); updateChapterSelection(positionOfCurrentChapter);
} }
@ -149,9 +153,9 @@ public class ChaptersFragment extends ListFragment {
if (position != -1 && focusedChapter != position) { if (position != -1 && focusedChapter != position) {
focusedChapter = position; focusedChapter = position;
adapter.notifyChapterChanged(focusedChapter); adapter.notifyChapterChanged(focusedChapter);
if (getListView().getFirstVisiblePosition() >= position if (layoutManager.findFirstCompletelyVisibleItemPosition() >= position
|| getListView().getLastVisiblePosition() <= position) { || layoutManager.findLastCompletelyVisibleItemPosition() <= position) {
getListView().setSelectionFromTop(position, 100); layoutManager.scrollToPositionWithOffset(position, 100);
} }
} }
} }

View File

@ -1,9 +1,7 @@
package de.danoeh.antennapod.fragment; package de.danoeh.antennapod.fragment;
import android.content.Intent; import android.content.Intent;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import androidx.core.app.ActivityOptionsCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -17,7 +15,9 @@ import android.widget.TextView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.ApGlideSettings;
@ -40,7 +40,6 @@ import org.greenrobot.eventbus.ThreadMode;
public class ExternalPlayerFragment extends Fragment { public class ExternalPlayerFragment extends Fragment {
public static final String TAG = "ExternalPlayerFragment"; public static final String TAG = "ExternalPlayerFragment";
private ViewGroup fragmentLayout;
private ImageView imgvCover; private ImageView imgvCover;
private TextView txtvTitle; private TextView txtvTitle;
private ImageButton butPlay; private ImageButton butPlay;
@ -56,26 +55,21 @@ public class ExternalPlayerFragment extends Fragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.external_player_fragment, View root = inflater.inflate(R.layout.external_player_fragment, container, false);
container, false);
fragmentLayout = root.findViewById(R.id.fragmentLayout);
imgvCover = root.findViewById(R.id.imgvCover); imgvCover = root.findViewById(R.id.imgvCover);
txtvTitle = root.findViewById(R.id.txtvTitle); txtvTitle = root.findViewById(R.id.txtvTitle);
butPlay = root.findViewById(R.id.butPlay); butPlay = root.findViewById(R.id.butPlay);
mFeedName = root.findViewById(R.id.txtvAuthor); mFeedName = root.findViewById(R.id.txtvAuthor);
mProgressBar = root.findViewById(R.id.episodeProgress); mProgressBar = root.findViewById(R.id.episodeProgress);
fragmentLayout.setOnClickListener(v -> { root.findViewById(R.id.fragmentLayout).setOnClickListener(v -> {
Log.d(TAG, "layoutInfo was clicked"); Log.d(TAG, "layoutInfo was clicked");
if (controller != null && controller.getMedia() != null) { if (controller != null && controller.getMedia() != null) {
Intent intent = PlaybackService.getPlayerActivityIntent(getActivity(), controller.getMedia());
if (controller.getMedia().getMediaType() == MediaType.AUDIO) { if (controller.getMedia().getMediaType() == MediaType.AUDIO) {
ActivityOptionsCompat options = ActivityOptionsCompat ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_EXPANDED);
.makeSceneTransitionAnimation(getActivity(), imgvCover, "coverTransition");
startActivity(intent, options.toBundle());
} else { } else {
Intent intent = PlaybackService.getPlayerActivityIntent(getActivity(), controller.getMedia());
startActivity(intent); startActivity(intent);
} }
} }
@ -178,9 +172,7 @@ public class ExternalPlayerFragment extends Fragment {
} }
private void playbackDone() { private void playbackDone() {
if (fragmentLayout != null) { clearUi();
fragmentLayout.setVisibility(View.GONE);
}
if (controller != null) { if (controller != null) {
controller.release(); controller.release();
} }
@ -217,10 +209,22 @@ public class ExternalPlayerFragment extends Fragment {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(media -> updateUi((Playable) media), .subscribe(media -> updateUi((Playable) media),
error -> Log.e(TAG, Log.getStackTraceString(error)), error -> Log.e(TAG, Log.getStackTraceString(error)),
() -> fragmentLayout.setVisibility(View.GONE)); this::clearUi);
return true; return true;
} }
private void clearUi() {
if (txtvTitle == null || mFeedName == null || mProgressBar == null || butPlay == null) {
return;
}
txtvTitle.setText(R.string.no_media_playing_label);
mFeedName.setText("");
butPlay.setVisibility(View.GONE);
mProgressBar.setProgress(0);
Glide.with(getActivity()).clear(imgvCover);
((MainActivity) getActivity()).getBottomSheet().setLocked(true);
}
private void updateUi(Playable media) { private void updateUi(Playable media) {
if (media != null) { if (media != null) {
txtvTitle.setText(media.getEpisodeTitle()); txtvTitle.setText(media.getEpisodeTitle());
@ -236,12 +240,13 @@ public class ExternalPlayerFragment extends Fragment {
.fitCenter() .fitCenter()
.dontAnimate()) .dontAnimate())
.into(imgvCover); .into(imgvCover);
fragmentLayout.setVisibility(View.VISIBLE);
if (controller != null && controller.isPlayingVideoLocally()) { if (controller != null && controller.isPlayingVideoLocally()) {
butPlay.setVisibility(View.GONE); butPlay.setVisibility(View.GONE);
((MainActivity) getActivity()).getBottomSheet().setLocked(true);
((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED);
} else { } else {
butPlay.setVisibility(View.VISIBLE); butPlay.setVisibility(View.VISIBLE);
((MainActivity) getActivity()).getBottomSheet().setLocked(false);
} }
} else { } else {
Log.w(TAG, "loadMediaInfo was called while the media object of playbackService was null!"); Log.w(TAG, "loadMediaInfo was called while the media object of playbackService was null!");

View File

@ -10,6 +10,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.core.util.playback.Timeline; import de.danoeh.antennapod.core.util.playback.Timeline;
import de.danoeh.antennapod.view.ShownotesWebView; import de.danoeh.antennapod.view.ShownotesWebView;
@ -35,7 +36,8 @@ public class ItemDescriptionFragment extends Fragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "Creating view"); Log.d(TAG, "Creating view");
webvDescription = new ShownotesWebView(getActivity().getApplicationContext()); View root = inflater.inflate(R.layout.item_description_fragment, container, false);
webvDescription = root.findViewById(R.id.webview);
webvDescription.setTimecodeSelectedListener(time -> { webvDescription.setTimecodeSelectedListener(time -> {
if (controller != null) { if (controller != null) {
controller.seekTo(time); controller.seekTo(time);
@ -46,7 +48,7 @@ public class ItemDescriptionFragment extends Fragment {
webvDescription.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50); webvDescription.postDelayed(ItemDescriptionFragment.this::restoreFromPreference, 50);
}); });
registerForContextMenu(webvDescription); registerForContextMenu(webvDescription);
return webvDescription; return root;
} }
@Override @Override

View File

@ -146,9 +146,6 @@ public class ItemPagerFragment extends Fragment {
return; return;
} }
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
if (Flavors.FLAVOR == Flavors.PLAY) {
((CastEnabledActivity) getActivity()).requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS);
}
inflater.inflate(R.menu.feeditem_options, menu); inflater.inflate(R.menu.feeditem_options, menu);
if (item.hasMedia()) { if (item.hasMedia()) {
FeedItemMenuHandler.onPrepareMenu(menu, item); FeedItemMenuHandler.onPrepareMenu(menu, item);

View File

@ -416,11 +416,11 @@ public class QueueFragment extends Fragment {
recyclerAdapter.setLocked(locked); recyclerAdapter.setLocked(locked);
} }
if (locked) { if (locked) {
Snackbar.make(getActivity().findViewById(R.id.content), R.string Snackbar.make(getActivity().findViewById(android.R.id.content),
.queue_locked, Snackbar.LENGTH_SHORT).show(); R.string.queue_locked, Snackbar.LENGTH_SHORT).show();
} else { } else {
Snackbar.make(getActivity().findViewById(R.id.content), R.string Snackbar.make(getActivity().findViewById(android.R.id.content),
.queue_unlocked, Snackbar.LENGTH_SHORT).show(); R.string.queue_unlocked, Snackbar.LENGTH_SHORT).show();
} }
} }

View File

@ -9,11 +9,11 @@ import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MediaplayerActivity;
import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
import de.danoeh.antennapod.dialog.VariableSpeedDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.preferences.PreferenceControllerFlavorHelper; import de.danoeh.antennapod.preferences.PreferenceControllerFlavorHelper;
import java.util.Map; import java.util.Map;
@ -48,11 +48,11 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat {
return true; return true;
}); });
findPreference(PREF_PLAYBACK_REWIND_DELTA_LAUNCHER).setOnPreferenceClickListener(preference -> { findPreference(PREF_PLAYBACK_REWIND_DELTA_LAUNCHER).setOnPreferenceClickListener(preference -> {
MediaplayerActivity.showSkipPreference(activity, MediaplayerActivity.SkipDirection.SKIP_REWIND); SkipPreferenceDialog.showSkipPreference(activity, SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null);
return true; return true;
}); });
findPreference(PREF_PLAYBACK_FAST_FORWARD_DELTA_LAUNCHER).setOnPreferenceClickListener(preference -> { findPreference(PREF_PLAYBACK_FAST_FORWARD_DELTA_LAUNCHER).setOnPreferenceClickListener(preference -> {
MediaplayerActivity.showSkipPreference(activity, MediaplayerActivity.SkipDirection.SKIP_FORWARD); SkipPreferenceDialog.showSkipPreference(activity, SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null);
return true; return true;
}); });
if (!PictureInPictureUtil.supportsPictureInPicture(activity)) { if (!PictureInPictureUtil.supportsPictureInPicture(activity)) {

View File

@ -0,0 +1,86 @@
package de.danoeh.antennapod.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import com.google.android.material.bottomsheet.ViewPagerBottomSheetBehavior;
/**
* Based on https://stackoverflow.com/a/40798214
*/
public class LockableBottomSheetBehavior<V extends View> extends ViewPagerBottomSheetBehavior<V> {
private boolean isLocked = false;
public LockableBottomSheetBehavior() {}
public LockableBottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setLocked(boolean locked) {
isLocked = locked;
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
boolean handled = false;
if (!isLocked) {
handled = super.onInterceptTouchEvent(parent, child, event);
}
return handled;
}
@Override
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
boolean handled = false;
if (!isLocked) {
handled = super.onTouchEvent(parent, child, event);
}
return handled;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild,
View target, int nestedScrollAxes) {
boolean handled = false;
if (!isLocked) {
handled = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}
return handled;
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target,
int dx, int dy, int[] consumed) {
if (!isLocked) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
}
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
if (!isLocked) {
super.onStopNestedScroll(coordinatorLayout, child, target);
}
}
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
float velocityX, float velocityY) {
boolean handled = false;
if (!isLocked) {
handled = super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
return handled;
}
}

View File

@ -1,7 +0,0 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="@android:color/transparent"
android:endColor="#40000000"
android:angle="90" />
</shape>

View File

@ -1,24 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout
xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize" android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme" android:theme="?attr/actionBarTheme"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
app:navigationIcon="?homeAsUpIndicator"
android:id="@+id/toolbar"/> android:id="@+id/toolbar"/>
<de.danoeh.antennapod.view.PagerIndicatorView <de.danoeh.antennapod.view.PagerIndicatorView
android:id="@+id/page_indicator" android:id="@+id/page_indicator"
android:layout_height="16dp" android:layout_height="16dp"
android:layout_width="40dp" android:layout_width="40dp"
@ -27,7 +24,16 @@
android:layout_below="@id/toolbar" android:layout_below="@id/toolbar"
android:layout_centerHorizontal="true"/> android:layout_centerHorizontal="true"/>
<androidx.viewpager.widget.ViewPager <FrameLayout
android:id="@+id/playerFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="?android:attr/windowBackground"
tools:layout_height="@dimen/external_player_height"
tools:background="@android:color/holo_green_light" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
@ -35,9 +41,9 @@
android:layout_below="@id/toolbar" android:layout_below="@id/toolbar"
android:foreground="?android:windowContentOverlay" android:foreground="?android:windowContentOverlay"
tools:background="@android:color/holo_orange_light" tools:background="@android:color/holo_orange_light"
android:layout_marginBottom="12dp" /> android:layout_marginBottom="12dp"/>
<SeekBar <SeekBar
android:id="@+id/sbPosition" android:id="@+id/sbPosition"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="24dp" android:layout_height="24dp"
@ -47,9 +53,9 @@
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:layoutDirection="ltr" android:layoutDirection="ltr"
tools:background="@android:color/holo_green_dark" /> tools:background="@android:color/holo_green_dark"/>
<LinearLayout <LinearLayout
android:id="@+id/playtime_layout" android:id="@+id/playtime_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -57,14 +63,14 @@
android:layoutDirection="ltr" android:layoutDirection="ltr"
android:orientation="vertical"> android:orientation="vertical">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:layout_marginBottom="4dp"> android:layout_marginBottom="4dp">
<TextView <TextView
android:id="@+id/txtvPosition" android:id="@+id/txtvPosition"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -75,9 +81,9 @@
android:text="@string/position_default_label" android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro" android:textSize="@dimen/text_size_micro"
tools:background="@android:color/holo_green_dark" /> tools:background="@android:color/holo_green_dark"/>
<TextView <TextView
android:id="@+id/txtvLength" android:id="@+id/txtvLength"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -88,18 +94,19 @@
android:text="@string/position_default_label" android:text="@string/position_default_label"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_micro" android:textSize="@dimen/text_size_micro"
tools:background="@android:color/holo_green_dark" /> android:background="?android:attr/selectableItemBackground"
tools:background="@android:color/holo_green_dark"/>
</RelativeLayout> </RelativeLayout>
<RelativeLayout <RelativeLayout
android:id="@+id/player_control" android:id="@+id/player_control"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
tools:background="@android:color/holo_purple"> tools:background="@android:color/holo_purple">
<ImageButton <ImageButton
android:id="@+id/butPlay" android:id="@+id/butPlay"
android:layout_width="@dimen/audioplayer_playercontrols_length_big" android:layout_width="@dimen/audioplayer_playercontrols_length_big"
android:layout_height="@dimen/audioplayer_playercontrols_length_big" android:layout_height="@dimen/audioplayer_playercontrols_length_big"
@ -115,9 +122,9 @@
app:srcCompat="?attr/av_play" app:srcCompat="?attr/av_play"
android:scaleType="fitCenter" android:scaleType="fitCenter"
tools:srcCompat="@drawable/ic_av_play_white_24dp" tools:srcCompat="@drawable/ic_av_play_white_24dp"
tools:background="@android:color/holo_green_dark" /> tools:background="@android:color/holo_green_dark"/>
<de.danoeh.antennapod.view.CircularProgressBar <de.danoeh.antennapod.view.CircularProgressBar
android:layout_width="@dimen/audioplayer_playercontrols_length_big" android:layout_width="@dimen/audioplayer_playercontrols_length_big"
android:layout_height="@dimen/audioplayer_playercontrols_length_big" android:layout_height="@dimen/audioplayer_playercontrols_length_big"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
@ -125,9 +132,18 @@
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_centerVertical="true" /> android:layout_centerVertical="true"/>
<ImageButton <ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="@dimen/audioplayer_playercontrols_length_big"
android:layout_height="@dimen/audioplayer_playercontrols_length_big"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:id="@+id/progLoading"
android:visibility="gone"/>
<ImageButton
android:id="@+id/butRev" android:id="@+id/butRev"
android:layout_width="@dimen/audioplayer_playercontrols_length" android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_height="@dimen/audioplayer_playercontrols_length"
@ -141,9 +157,9 @@
app:srcCompat="?attr/av_rewind" app:srcCompat="?attr/av_rewind"
android:scaleType="fitCenter" android:scaleType="fitCenter"
tools:srcCompat="@drawable/ic_av_fast_rewind_white_48dp" tools:srcCompat="@drawable/ic_av_fast_rewind_white_48dp"
tools:background="@android:color/holo_blue_dark" /> tools:background="@android:color/holo_blue_dark"/>
<TextView <TextView
android:id="@+id/txtvRev" android:id="@+id/txtvRev"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -156,9 +172,9 @@
android:text="30" android:text="30"
android:textSize="12sp" android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:clickable="false" /> android:clickable="false"/>
<de.danoeh.antennapod.view.PlaybackSpeedIndicatorView <de.danoeh.antennapod.view.PlaybackSpeedIndicatorView
android:id="@+id/butPlaybackSpeed" android:id="@+id/butPlaybackSpeed"
android:layout_width="@dimen/audioplayer_playercontrols_length" android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_height="@dimen/audioplayer_playercontrols_length"
@ -169,9 +185,9 @@
android:contentDescription="@string/set_playback_speed_label" android:contentDescription="@string/set_playback_speed_label"
tools:srcCompat="@drawable/ic_playback_speed_white" tools:srcCompat="@drawable/ic_playback_speed_white"
tools:visibility="gone" tools:visibility="gone"
tools:background="@android:color/holo_green_dark" /> tools:background="@android:color/holo_green_dark"/>
<TextView <TextView
android:id="@+id/txtvPlaybackSpeed" android:id="@+id/txtvPlaybackSpeed"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -184,9 +200,9 @@
android:text="1.00" android:text="1.00"
android:textSize="12sp" android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:clickable="false" /> android:clickable="false"/>
<ImageButton <ImageButton
android:id="@+id/butFF" android:id="@+id/butFF"
android:layout_width="@dimen/audioplayer_playercontrols_length" android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_height="@dimen/audioplayer_playercontrols_length"
@ -200,9 +216,9 @@
app:srcCompat="?attr/av_fast_forward" app:srcCompat="?attr/av_fast_forward"
android:scaleType="fitCenter" android:scaleType="fitCenter"
tools:srcCompat="@drawable/ic_av_fast_forward_white_48dp" tools:srcCompat="@drawable/ic_av_fast_forward_white_48dp"
tools:background="@android:color/holo_blue_dark" /> tools:background="@android:color/holo_blue_dark"/>
<TextView <TextView
android:id="@+id/txtvFF" android:id="@+id/txtvFF"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -215,9 +231,9 @@
android:text="30" android:text="30"
android:textSize="12sp" android:textSize="12sp"
android:textColor="?android:attr/textColorSecondary" android:textColor="?android:attr/textColorSecondary"
android:clickable="false" /> android:clickable="false"/>
<ImageButton <ImageButton
android:id="@+id/butSkip" android:id="@+id/butSkip"
android:layout_width="@dimen/audioplayer_playercontrols_length" android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="@dimen/audioplayer_playercontrols_length" android:layout_height="@dimen/audioplayer_playercontrols_length"
@ -229,20 +245,9 @@
app:srcCompat="?attr/av_skip" app:srcCompat="?attr/av_skip"
android:contentDescription="@string/skip_episode_label" android:contentDescription="@string/skip_episode_label"
tools:srcCompat="@drawable/ic_av_skip_white_48dp" tools:srcCompat="@drawable/ic_av_skip_white_48dp"
tools:background="@android:color/holo_green_dark" /> tools:background="@android:color/holo_green_dark"/>
</RelativeLayout> </RelativeLayout>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>
<FrameLayout
android:id="@+id/navDrawerFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginEnd="24dp"
android:layout_marginRight="24dp"
android:layout_gravity="start"
android:orientation="vertical" />
</androidx.drawerlayout.widget.DrawerLayout>

View File

@ -1,87 +1,84 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragmentLayout" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:id="@+id/fragmentLayout"
android:layout_height="@dimen/external_player_height" android:layout_width="match_parent"
android:visibility="gone"
android:background="?attr/selectableItemBackground">
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/external_player_height"
android:layout_height="@dimen/external_player_height" android:layout_height="@dimen/external_player_height"
android:adjustViewBounds="true" android:background="?attr/selectableItemBackground"
android:cropToPadding="true" android:orientation="vertical">
android:scaleType="centerCrop"
tools:src="@drawable/ic_drag_vertical_white_48dp" <View
tools:background="@android:color/holo_green_dark" android:layout_width="match_parent"
android:transitionName="coverTransition" android:layout_height="1dp"
android:layout_alignParentTop="true" android:background="?android:attr/dividerHorizontal" />
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"/> <LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:gravity="center_vertical"
android:layout_weight="1">
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:cropToPadding="true"
android:scaleType="centerCrop"
tools:src="@tools:sample/avatars"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp">
<TextView
android:id="@+id/txtvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Base.TextAppearance.AppCompat.Body1"
android:ellipsize="end"
android:maxLines="1"
tools:text="Episode title that is too long and will cause the text to wrap"/>
<TextView
android:id="@+id/txtvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Body1"
android:textColor="?android:attr/textColorSecondary"
android:ellipsize="end"
android:maxLines="1"
tools:text="Episode author that is too long and will cause the text to wrap"/>
</LinearLayout>
<ImageButton
android:id="@+id/butPlay"
android:layout_width="52dp"
android:layout_height="match_parent"
android:contentDescription="@string/pause_label"
android:background="?attr/selectableItemBackground"
app:srcCompat="?attr/av_play"
android:scaleType="fitCenter"
android:padding="8dp"
tools:src="@drawable/ic_play_arrow_white_36dp"/>
</LinearLayout>
<ProgressBar <ProgressBar
android:id="@+id/episodeProgress" android:id="@+id/episodeProgress"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="4dp" android:layout_height="4dp"
android:layout_toRightOf="@id/imgvCover" style="?attr/progressBarTheme"
android:layout_toEndOf="@id/imgvCover" android:indeterminate="false"
android:layout_alignParentTop="true" tools:progress="100"/>
style="?attr/progressBarTheme"
android:indeterminate="false"
tools:progress="100"/>
<ImageButton </LinearLayout>
android:id="@+id/butPlay"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_below="@id/episodeProgress"
android:layout_centerVertical="true"
android:contentDescription="@string/pause_label"
android:background="?attr/selectableItemBackground"
app:srcCompat="?attr/av_play"
android:scaleType="fitCenter"
android:padding="8dp"
tools:src="@drawable/ic_play_arrow_white_36dp"/>
<TextView
android:id="@+id/txtvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="26dp"
android:layout_toRightOf="@id/imgvCover"
android:layout_toEndOf="@id/imgvCover"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_toLeftOf="@id/butPlay"
android:layout_toStartOf="@id/butPlay"
style="@style/Base.TextAppearance.AppCompat.Body1"
android:ellipsize="end"
android:maxLines="1"
tools:text="Episode title that is too long and will cause the text to wrap"/>
<TextView
android:id="@+id/txtvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/episodeProgress"
android:layout_marginTop="26dp"
android:layout_toRightOf="@id/imgvCover"
android:layout_toEndOf="@id/imgvCover"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"
android:layout_toLeftOf="@id/butPlay"
android:layout_toStartOf="@id/butPlay"
style="@style/TextAppearance.AppCompat.Body1"
android:textColor="?android:attr/textColorSecondary"
android:ellipsize="end"
android:maxLines="1"
tools:text="Episode author that is too long and will cause the text to wrap"/>
</RelativeLayout>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="false">
<de.danoeh.antennapod.view.ShownotesWebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</androidx.core.widget.NestedScrollView>

View File

@ -1,42 +1,43 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout <androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:id="@+id/drawer_layout"
android:layout_height="match_parent">
<RelativeLayout
android:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<FrameLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/playerFragment" android:id="@+id/overview_coordinator_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:layout_alignParentBottom="true"
tools:layout_height="64dp"
tools:background="@android:color/holo_green_light" />
<FrameLayout <FrameLayout
android:id="@+id/main_view" android:id="@+id/main_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0px" android:layout_height="match_parent"
android:layout_above="@id/playerFragment"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:foreground="?android:windowContentOverlay" android:foreground="?android:windowContentOverlay"
android:layout_marginBottom="@dimen/external_player_height"
tools:background="@android:color/holo_red_dark" /> tools:background="@android:color/holo_red_dark" />
</RelativeLayout> <FrameLayout
android:elevation="8dp"
android:id="@+id/audioplayerFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
app:layout_behavior="de.danoeh.antennapod.view.LockableBottomSheetBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<FrameLayout <FrameLayout
android:id="@+id/navDrawerFragment" android:id="@+id/navDrawerFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginEnd="24dp" android:layout_marginEnd="24dp"
android:layout_marginRight="24dp" android:layout_marginRight="24dp"
android:layout_gravity="start" android:layout_gravity="start"
android:orientation="vertical" /> android:orientation="vertical" />
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

View File

@ -4,8 +4,6 @@ import android.content.SharedPreferences;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import androidx.annotation.CallSuper;
import androidx.core.view.MenuItemCompat;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
@ -14,6 +12,7 @@ import android.view.MenuItem;
import com.google.android.gms.cast.ApplicationMetadata; import com.google.android.gms.cast.ApplicationMetadata;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.cast.CastButtonVisibilityManager;
import de.danoeh.antennapod.core.cast.CastConsumer; import de.danoeh.antennapod.core.cast.CastConsumer;
import de.danoeh.antennapod.core.cast.CastManager; import de.danoeh.antennapod.core.cast.CastManager;
import de.danoeh.antennapod.core.cast.DefaultCastConsumer; import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
@ -21,6 +20,9 @@ import de.danoeh.antennapod.core.cast.SwitchableMediaRouteActionProvider;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlaybackService;
import java.util.ArrayList;
import java.util.List;
/** /**
* Activity that allows for showing the MediaRouter button whenever there's a cast device in the * Activity that allows for showing the MediaRouter button whenever there's a cast device in the
* network. * network.
@ -31,9 +33,7 @@ public abstract class CastEnabledActivity extends AppCompatActivity
private CastConsumer castConsumer; private CastConsumer castConsumer;
private CastManager castManager; private CastManager castManager;
private final List<CastButtonVisibilityManager> castButtons = new ArrayList<>();
private SwitchableMediaRouteActionProvider mediaRouteActionProvider;
private final CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -59,8 +59,10 @@ public abstract class CastEnabledActivity extends AppCompatActivity
}; };
castManager = CastManager.getInstance(); castManager = CastManager.getInstance();
castManager.addCastConsumer(castConsumer); castManager.addCastConsumer(castConsumer);
CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager(castManager);
castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled()); castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled());
onCastConnectionChanged(castManager.isConnected()); onCastConnectionChanged(castManager.isConnected());
castButtons.add(castButtonVisibilityManager);
} }
@Override @Override
@ -75,46 +77,15 @@ public abstract class CastEnabledActivity extends AppCompatActivity
super.onDestroy(); super.onDestroy();
} }
@Override
@CallSuper
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
if (!CastManager.isInitialized()) {
return true;
}
getMenuInflater().inflate(R.menu.cast_enabled, menu);
castButtonVisibilityManager.setMenu(menu);
return true;
}
@Override
@CallSuper
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
if (!CastManager.isInitialized()) {
return true;
}
MenuItem mediaRouteButton = menu.findItem(R.id.media_route_menu_item);
if (mediaRouteButton == null) {
Log.wtf(TAG, "MediaRoute item could not be found on the menu!", new Exception());
mediaRouteActionProvider = null;
return true;
}
mediaRouteActionProvider = castManager.addMediaRouterButton(mediaRouteButton);
if (mediaRouteActionProvider != null) {
mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable());
}
return true;
}
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
if (!CastManager.isInitialized()) { if (!CastManager.isInitialized()) {
return; return;
} }
castButtonVisibilityManager.setResumed(true); for (CastButtonVisibilityManager castButton : castButtons) {
castButton.setResumed(true);
}
} }
@Override @Override
@ -123,7 +94,9 @@ public abstract class CastEnabledActivity extends AppCompatActivity
if (!CastManager.isInitialized()) { if (!CastManager.isInitialized()) {
return; return;
} }
castButtonVisibilityManager.setResumed(false); for (CastButtonVisibilityManager castButton : castButtons) {
castButton.setResumed(false);
}
} }
@ -132,7 +105,9 @@ public abstract class CastEnabledActivity extends AppCompatActivity
if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { if (UserPreferences.PREF_CAST_ENABLED.equals(key)) {
boolean newValue = UserPreferences.isCastEnabled(); boolean newValue = UserPreferences.isCastEnabled();
Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + newValue); Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + newValue);
castButtonVisibilityManager.setPrefEnabled(newValue); for (CastButtonVisibilityManager castButton : castButtons) {
castButton.setPrefEnabled(newValue);
}
// PlaybackService has its own listener, so if it's active we don't have to take action here. // PlaybackService has its own listener, so if it's active we don't have to take action here.
if (!newValue && !PlaybackService.isRunning) { if (!newValue && !PlaybackService.isRunning) {
CastManager.getInstance().disconnect(); CastManager.getInstance().disconnect();
@ -142,129 +117,41 @@ public abstract class CastEnabledActivity extends AppCompatActivity
private void onCastConnectionChanged(boolean connected) { private void onCastConnectionChanged(boolean connected) {
if (connected) { if (connected) {
castButtonVisibilityManager.onConnected(); for (CastButtonVisibilityManager castButton : castButtons) {
castButton.onConnected();
}
setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE); setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
} else { } else {
castButtonVisibilityManager.onDisconnected(); for (CastButtonVisibilityManager castButton : castButtons) {
castButton.onDisconnected();
}
setVolumeControlStream(AudioManager.STREAM_MUSIC); setVolumeControlStream(AudioManager.STREAM_MUSIC);
} }
} }
/** /**
* Should be called by any activity or fragment for which the cast button should be shown. * Should be called by any activity or fragment for which the cast button should be shown.
*
* @param showAsAction refer to {@link MenuItem#setShowAsAction(int)}
*/ */
public final void requestCastButton(int showAsAction) { public final void requestCastButton(Menu menu) {
if (!CastManager.isInitialized()) { if (!CastManager.isInitialized()) {
return; return;
} }
castButtonVisibilityManager.requestCastButton(showAsAction);
}
private class CastButtonVisibilityManager { MenuItem mediaRouteButton = menu.findItem(R.id.media_route_menu_item);
private volatile boolean prefEnabled = false; if (mediaRouteButton == null) {
private volatile boolean viewRequested = false; getMenuInflater().inflate(R.menu.cast_enabled, menu);
private volatile boolean resumed = false; mediaRouteButton = menu.findItem(R.id.media_route_menu_item);
private volatile boolean connected = false;
private volatile int showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
private Menu menu;
public synchronized void setPrefEnabled(boolean newValue) {
if (prefEnabled != newValue && resumed && (viewRequested || connected)) {
if (newValue) {
castManager.incrementUiCounter();
} else {
castManager.decrementUiCounter();
}
}
prefEnabled = newValue;
if (mediaRouteActionProvider != null) {
mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
}
} }
public synchronized void setResumed(boolean newValue) { SwitchableMediaRouteActionProvider mediaRouteActionProvider =
if (resumed == newValue) { CastManager.getInstance().addMediaRouterButton(mediaRouteButton);
Log.e(TAG, "resumed should never change to the same value"); CastButtonVisibilityManager castButtonVisibilityManager =
return; new CastButtonVisibilityManager(CastManager.getInstance());
} castButtonVisibilityManager.setMenu(menu);
resumed = newValue; castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled());
if (prefEnabled && (viewRequested || connected)) { castButtonVisibilityManager.mediaRouteActionProvider = mediaRouteActionProvider;
if (resumed) { castButtonVisibilityManager.setResumed(true);
castManager.incrementUiCounter(); castButtonVisibilityManager.requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS);
} else { mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable());
castManager.decrementUiCounter();
}
}
}
public synchronized void setViewRequested(boolean newValue) {
if (viewRequested != newValue && resumed && prefEnabled && !connected) {
if (newValue) {
castManager.incrementUiCounter();
} else {
castManager.decrementUiCounter();
}
}
viewRequested = newValue;
if (mediaRouteActionProvider != null) {
mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
}
}
public synchronized void setConnected(boolean newValue) {
if (connected != newValue && resumed && prefEnabled && !prefEnabled) {
if (newValue) {
castManager.incrementUiCounter();
} else {
castManager.decrementUiCounter();
}
}
connected = newValue;
if (mediaRouteActionProvider != null) {
mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
}
}
public synchronized boolean shouldEnable() {
return prefEnabled && viewRequested;
}
public void setMenu(Menu menu) {
setViewRequested(false);
showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
this.menu = menu;
setShowAsAction();
}
public void requestCastButton(int showAsAction) {
setViewRequested(true);
this.showAsAction = showAsAction;
setShowAsAction();
}
public void onConnected() {
setConnected(true);
setShowAsAction();
}
public void onDisconnected() {
setConnected(false);
setShowAsAction();
}
private void setShowAsAction() {
if (menu == null) {
Log.d(TAG, "setShowAsAction() without a menu");
return;
}
MenuItem item = menu.findItem(R.id.media_route_menu_item);
if (item == null) {
Log.e(TAG, "setShowAsAction(), but cast button not inflated");
return;
}
MenuItemCompat.setShowAsAction(item, connected? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction);
}
} }
} }

View File

@ -181,11 +181,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/ */
public static final int NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED = 9; public static final int NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED = 9;
/**
* Send a message to the user (with provided String resource id)
*/
public static final int NOTIFICATION_TYPE_SHOW_TOAST = 10;
/** /**
* Returned by getPositionSafe() or getDurationSafe() if the playbackService * Returned by getPositionSafe() or getDurationSafe() if the playbackService
* is in an invalid state. * is in an invalid state.
@ -1218,7 +1213,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
notificationBuilder.setCasting(isCasting); notificationBuilder.setCasting(isCasting);
notificationBuilder.updatePosition(getCurrentPosition(), getCurrentPlaybackSpeed()); notificationBuilder.updatePosition(getCurrentPosition(), getCurrentPlaybackSpeed());
Log.d(TAG, "setupNotification: startForeground" + playerStatus);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
startForegroundIfPlaying(playerStatus); startForegroundIfPlaying(playerStatus);
@ -1236,10 +1230,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} }
private void startForegroundIfPlaying(@NonNull PlayerStatus status) { private void startForegroundIfPlaying(@NonNull PlayerStatus status) {
Log.d(TAG, "startForegroundIfPlaying: " + status);
if (stateManager.hasReceivedValidStartCommand()) { if (stateManager.hasReceivedValidStartCommand()) {
if (isCasting || status == PlayerStatus.PLAYING || status == PlayerStatus.PREPARING if (isCasting || status == PlayerStatus.PLAYING || status == PlayerStatus.PREPARING
|| status == PlayerStatus.SEEKING) { || status == PlayerStatus.SEEKING) {
stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build()); stateManager.startForeground(NOTIFICATION_ID, notificationBuilder.build());
Log.d(TAG, "foreground");
} else { } else {
stateManager.stopForeground(false); stateManager.stopForeground(false);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
@ -1781,6 +1777,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override @Override
public void setIsCasting(boolean isCasting) { public void setIsCasting(boolean isCasting) {
PlaybackService.isCasting = isCasting; PlaybackService.isCasting = isCasting;
stateManager.validStartCommandWasReceived();
} }
@Override @Override

View File

@ -21,6 +21,7 @@ import android.widget.ImageButton;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
import de.danoeh.antennapod.core.event.MessageEvent;
import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.core.util.ThemeUtils;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
@ -333,8 +334,6 @@ public class PlaybackController {
case PlaybackService.NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED: case PlaybackService.NOTIFICATION_TYPE_SET_SPEED_ABILITY_CHANGED:
onSetSpeedAbilityChanged(); onSetSpeedAbilityChanged();
break; break;
case PlaybackService.NOTIFICATION_TYPE_SHOW_TOAST:
postStatusMsg(code, true);
} }
} }
@ -411,11 +410,10 @@ public class PlaybackController {
Log.d(TAG, "status: " + status.toString()); Log.d(TAG, "status: " + status.toString());
switch (status) { switch (status) {
case ERROR: case ERROR:
postStatusMsg(R.string.player_error_msg, false); EventBus.getDefault().post(new MessageEvent(activity.getString(R.string.player_error_msg)));
handleError(MediaPlayer.MEDIA_ERROR_UNKNOWN); handleError(MediaPlayer.MEDIA_ERROR_UNKNOWN);
break; break;
case PAUSED: case PAUSED:
clearStatusMsg();
checkMediaInfoLoaded(); checkMediaInfoLoaded();
onPositionObserverUpdate(); onPositionObserverUpdate();
updatePlayButtonAppearance(playResource, playText); updatePlayButtonAppearance(playResource, playText);
@ -425,7 +423,6 @@ public class PlaybackController {
} }
break; break;
case PLAYING: case PLAYING:
clearStatusMsg();
checkMediaInfoLoaded(); checkMediaInfoLoaded();
if (!PlaybackService.isCasting() && if (!PlaybackService.isCasting() &&
PlaybackService.getCurrentMediaType() == MediaType.VIDEO) { PlaybackService.getCurrentMediaType() == MediaType.VIDEO) {
@ -435,7 +432,6 @@ public class PlaybackController {
updatePlayButtonAppearance(pauseResource, pauseText); updatePlayButtonAppearance(pauseResource, pauseText);
break; break;
case PREPARING: case PREPARING:
postStatusMsg(R.string.player_preparing_msg, false);
checkMediaInfoLoaded(); checkMediaInfoLoaded();
if (playbackService != null) { if (playbackService != null) {
if (playbackService.isStartWhenPrepared()) { if (playbackService.isStartWhenPrepared()) {
@ -446,21 +442,17 @@ public class PlaybackController {
} }
break; break;
case STOPPED: case STOPPED:
postStatusMsg(R.string.player_stopped_msg, false);
break; break;
case PREPARED: case PREPARED:
checkMediaInfoLoaded(); checkMediaInfoLoaded();
postStatusMsg(R.string.player_ready_msg, false);
updatePlayButtonAppearance(playResource, playText); updatePlayButtonAppearance(playResource, playText);
onPositionObserverUpdate(); onPositionObserverUpdate();
break; break;
case SEEKING: case SEEKING:
onPositionObserverUpdate(); onPositionObserverUpdate();
postStatusMsg(R.string.player_seeking_msg, false);
break; break;
case INITIALIZED: case INITIALIZED:
checkMediaInfoLoaded(); checkMediaInfoLoaded();
clearStatusMsg();
updatePlayButtonAppearance(playResource, playText); updatePlayButtonAppearance(playResource, playText);
break; break;
} }
@ -482,10 +474,6 @@ public class PlaybackController {
return null; return null;
} }
public void postStatusMsg(int msg, boolean showToast) {}
public void clearStatusMsg() {}
public boolean loadMediaInfo() { public boolean loadMediaInfo() {
return false; return false;
} }
@ -599,8 +587,8 @@ public class PlaybackController {
public int getPosition() { public int getPosition() {
if (playbackService != null) { if (playbackService != null) {
return playbackService.getCurrentPosition(); return playbackService.getCurrentPosition();
} else if (media != null) { } else if (getMedia() != null) {
return media.getPosition(); return getMedia().getPosition();
} else { } else {
return PlaybackService.INVALID_TIME; return PlaybackService.INVALID_TIME;
} }
@ -609,8 +597,8 @@ public class PlaybackController {
public int getDuration() { public int getDuration() {
if (playbackService != null) { if (playbackService != null) {
return playbackService.getDuration(); return playbackService.getDuration();
} else if (media != null) { } else if (getMedia() != null) {
return media.getDuration(); return getMedia().getDuration();
} else { } else {
return PlaybackService.INVALID_TIME; return PlaybackService.INVALID_TIME;
} }

View File

@ -3,7 +3,7 @@
<dimen name="widget_margin">0dp</dimen> <dimen name="widget_margin">0dp</dimen>
<dimen name="thumbnail_length">70dp</dimen> <dimen name="thumbnail_length">70dp</dimen>
<dimen name="external_player_height">56dp</dimen> <dimen name="external_player_height">64dp</dimen>
<dimen name="enc_icons_size">20dp</dimen> <dimen name="enc_icons_size">20dp</dimen>
<dimen name="text_size_micro">12sp</dimen> <dimen name="text_size_micro">12sp</dimen>
<dimen name="text_size_small">14sp</dimen> <dimen name="text_size_small">14sp</dimen>

View File

@ -0,0 +1,120 @@
package de.danoeh.antennapod.core.cast;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import androidx.core.view.MenuItemCompat;
import de.danoeh.antennapod.core.R;
public class CastButtonVisibilityManager {
private static final String TAG = "CastBtnVisibilityMgr";
private final CastManager castManager;
private volatile boolean prefEnabled = false;
private volatile boolean viewRequested = false;
private volatile boolean resumed = false;
private volatile boolean connected = false;
private volatile int showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
private Menu menu;
public SwitchableMediaRouteActionProvider mediaRouteActionProvider;
public CastButtonVisibilityManager(CastManager castManager) {
this.castManager = castManager;
}
public synchronized void setPrefEnabled(boolean newValue) {
if (prefEnabled != newValue && resumed && (viewRequested || connected)) {
if (newValue) {
castManager.incrementUiCounter();
} else {
castManager.decrementUiCounter();
}
}
prefEnabled = newValue;
if (mediaRouteActionProvider != null) {
mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
}
}
public synchronized void setResumed(boolean newValue) {
if (resumed == newValue) {
Log.e(TAG, "resumed should never change to the same value");
return;
}
resumed = newValue;
if (prefEnabled && (viewRequested || connected)) {
if (resumed) {
castManager.incrementUiCounter();
} else {
castManager.decrementUiCounter();
}
}
}
public synchronized void setViewRequested(boolean newValue) {
if (viewRequested != newValue && resumed && prefEnabled && !connected) {
if (newValue) {
castManager.incrementUiCounter();
} else {
castManager.decrementUiCounter();
}
}
viewRequested = newValue;
if (mediaRouteActionProvider != null) {
mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
}
}
public synchronized void setConnected(boolean newValue) {
if (connected != newValue && resumed && prefEnabled && !prefEnabled) {
if (newValue) {
castManager.incrementUiCounter();
} else {
castManager.decrementUiCounter();
}
}
connected = newValue;
if (mediaRouteActionProvider != null) {
mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
}
}
public synchronized boolean shouldEnable() {
return prefEnabled && viewRequested;
}
public void setMenu(Menu menu) {
setViewRequested(false);
showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
this.menu = menu;
setShowAsAction();
}
public void requestCastButton(int showAsAction) {
setViewRequested(true);
this.showAsAction = showAsAction;
setShowAsAction();
}
public void onConnected() {
setConnected(true);
setShowAsAction();
}
public void onDisconnected() {
setConnected(false);
setShowAsAction();
}
private void setShowAsAction() {
if (menu == null) {
Log.d(TAG, "setShowAsAction() without a menu");
return;
}
MenuItem item = menu.findItem(R.id.media_route_menu_item);
if (item == null) {
Log.e(TAG, "setShowAsAction(), but cast button not inflated");
return;
}
MenuItemCompat.setShowAsAction(item, connected ? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction);
}
}

View File

@ -26,9 +26,11 @@ import java.util.concurrent.TimeoutException;
import de.danoeh.antennapod.core.cast.CastConsumer; import de.danoeh.antennapod.core.cast.CastConsumer;
import de.danoeh.antennapod.core.cast.CastManager; import de.danoeh.antennapod.core.cast.CastManager;
import de.danoeh.antennapod.core.cast.DefaultCastConsumer; import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
import de.danoeh.antennapod.core.event.MessageEvent;
import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.NetworkUtils;
import org.greenrobot.eventbus.EventBus;
/** /**
* Class intended to work along PlaybackService and provide support for different flavors. * Class intended to work along PlaybackService and provide support for different flavors.
@ -106,7 +108,7 @@ public class PlaybackServiceFlavorHelper {
} }
switch (code) { switch (code) {
case RemotePSMP.CAST_ERROR: case RemotePSMP.CAST_ERROR:
callback.sendNotificationBroadcast(PlaybackService.NOTIFICATION_TYPE_SHOW_TOAST, resourceId); EventBus.getDefault().post(new MessageEvent(context.getString(resourceId)));
return true; return true;
case RemotePSMP.CAST_ERROR_PRIORITY_HIGH: case RemotePSMP.CAST_ERROR_PRIORITY_HIGH:
Toast.makeText(context, resourceId, Toast.LENGTH_SHORT).show(); Toast.makeText(context, resourceId, Toast.LENGTH_SHORT).show();