diff options
Diffstat (limited to 'app/src')
113 files changed, 2270 insertions, 2348 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java index 9f7af3a16..2ab2361d7 100644 --- a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java +++ b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java @@ -11,6 +11,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import de.danoeh.antennapod.model.feed.FeedItemFilter; +import de.danoeh.antennapod.playback.base.PlayerStatus; import org.awaitility.Awaitility; import org.hamcrest.Matcher; import org.junit.After; @@ -32,7 +33,6 @@ import de.danoeh.antennapod.model.feed.FeedMedia; 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.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.IntentUtils; diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java b/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java index ae38fd5e7..4d57b9b43 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/CancelablePSMPCallback.java @@ -1,9 +1,10 @@ package de.test.antennapod.service.playback; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import de.danoeh.antennapod.model.playback.MediaType; -import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; import de.danoeh.antennapod.model.playback.Playable; +import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; public class CancelablePSMPCallback implements PlaybackServiceMediaPlayer.PSMPCallback { @@ -35,22 +36,6 @@ public class CancelablePSMPCallback implements PlaybackServiceMediaPlayer.PSMPCa } @Override - public void playbackSpeedChanged(float s) { - if (isCancelled) { - return; - } - originalCallback.playbackSpeedChanged(s); - } - - @Override - public void onBufferingUpdate(int percent) { - if (isCancelled) { - return; - } - originalCallback.onBufferingUpdate(percent); - } - - @Override public void onMediaChanged(boolean reloadUI) { if (isCancelled) { return; @@ -59,22 +44,6 @@ public class CancelablePSMPCallback implements PlaybackServiceMediaPlayer.PSMPCa } @Override - public boolean onMediaPlayerInfo(int code, int resourceId) { - if (isCancelled) { - return true; - } - return originalCallback.onMediaPlayerInfo(code, resourceId); - } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - if (isCancelled) { - return true; - } - return originalCallback.onMediaPlayerError(inObj, what, extra); - } - - @Override public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) { if (isCancelled) { return; @@ -106,6 +75,15 @@ public class CancelablePSMPCallback implements PlaybackServiceMediaPlayer.PSMPCa return originalCallback.getNextInQueue(currentMedia); } + @Nullable + @Override + public Playable findMedia(@NonNull String url) { + if (isCancelled) { + return null; + } + return originalCallback.findMedia(url); + } + @Override public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) { if (isCancelled) { @@ -113,4 +91,12 @@ public class CancelablePSMPCallback implements PlaybackServiceMediaPlayer.PSMPCa } originalCallback.onPlaybackEnded(mediaType, stopPlaying); } + + @Override + public void ensureMediaInfoLoaded(@NonNull Playable media) { + if (isCancelled) { + return; + } + originalCallback.ensureMediaInfoLoaded(media); + } }
\ No newline at end of file diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java b/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java index 29a854f20..fb55c7ad0 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/DefaultPSMPCallback.java @@ -1,69 +1,59 @@ package de.test.antennapod.service.playback; import androidx.annotation.NonNull; -import androidx.annotation.StringRes; +import androidx.annotation.Nullable; import de.danoeh.antennapod.model.playback.MediaType; -import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; import de.danoeh.antennapod.model.playback.Playable; +import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; public class DefaultPSMPCallback implements PlaybackServiceMediaPlayer.PSMPCallback { - @Override - public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { + @Override + public void statusChanged(PlaybackServiceMediaPlayer.PSMPInfo newInfo) { - } + } - @Override - public void shouldStop() { + @Override + public void shouldStop() { - } + } - @Override - public void playbackSpeedChanged(float s) { + @Override + public void onMediaChanged(boolean reloadUI) { - } + } - @Override - public void onBufferingUpdate(int percent) { + @Override + public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) { - } + } - @Override - public void onMediaChanged(boolean reloadUI) { + @Override + public void onPlaybackStart(@NonNull Playable playable, int position) { - } + } - @Override - public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) { - return false; - } + @Override + public void onPlaybackPause(Playable playable, int position) { - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - return false; - } + } - @Override - public void onPostPlayback(@NonNull Playable media, boolean ended, boolean skipped, boolean playingNext) { + @Override + public Playable getNextInQueue(Playable currentMedia) { + return null; + } - } + @Nullable + @Override + public Playable findMedia(@NonNull String url) { + return null; + } - @Override - public void onPlaybackStart(@NonNull Playable playable, int position) { + @Override + public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) { - } + } - @Override - public void onPlaybackPause(Playable playable, int position) { - - } - - @Override - public Playable getNextInQueue(Playable currentMedia) { - return null; - } - - @Override - public void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) { - - } - }
\ No newline at end of file + @Override + public void ensureMediaInfoLoaded(@NonNull Playable media) { + } +}
\ No newline at end of file diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java index dfb0e3e36..32298200e 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceMediaPlayerTest.java @@ -5,6 +5,8 @@ import android.content.Context; import androidx.test.filters.MediumTest; import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting; +import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer; +import de.danoeh.antennapod.playback.base.PlayerStatus; import de.test.antennapod.EspressoTestUtils; import junit.framework.AssertionFailedError; @@ -24,8 +26,6 @@ import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.feed.FeedPreferences; import de.danoeh.antennapod.core.service.playback.LocalPSMP; -import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.PodDBAdapter; import de.danoeh.antennapod.model.playback.Playable; import de.test.antennapod.util.service.download.HTTPBin; @@ -514,13 +514,6 @@ public class PlaybackServiceMediaPlayerTest { if (assertionError == null) assertionError = new AssertionFailedError("Unexpected call to shouldStop"); } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - if (assertionError == null) - assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError"); - return false; - } }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL); @@ -604,14 +597,6 @@ public class PlaybackServiceMediaPlayerTest { } } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - if (assertionError == null) { - assertionError = new AssertionFailedError("Unexpected call of onMediaPlayerError"); - } - return false; - } }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) { @@ -664,13 +649,6 @@ public class PlaybackServiceMediaPlayerTest { } } } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - if (assertionError == null) - assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError"); - return false; - } }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL); @@ -738,13 +716,6 @@ public class PlaybackServiceMediaPlayerTest { } } } - - @Override - public boolean onMediaPlayerError(Object inObj, int what, int extra) { - if (assertionError == null) - assertionError = new AssertionFailedError("Unexpected call to onMediaPlayerError"); - return false; - } }); PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback); Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL); diff --git a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java index 7803144e1..013d4db50 100644 --- a/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java @@ -5,10 +5,12 @@ import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.LargeTest; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.core.widget.WidgetUpdater; import org.awaitility.Awaitility; import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -19,7 +21,7 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import de.danoeh.antennapod.core.event.QueueEvent; +import de.danoeh.antennapod.event.QueueEvent; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; @@ -144,7 +146,7 @@ public class PlaybackServiceTaskManagerTest { FeedItem item = DBReader.getFeedItem(testItem.getId()); item.getMedia().setDownloaded(true); item.getMedia().setFile_url("file://123"); - item.setAutoDownload(false); + item.disableAutoDownload(); DBWriter.setFeedMedia(item.getMedia()).get(); DBWriter.setFeedItem(item).get(); @@ -173,21 +175,6 @@ public class PlaybackServiceTaskManagerTest { } @Override - public void onSleepTimerAlmostExpired(long timeLeft) { - - } - - @Override - public void onSleepTimerExpired() { - - } - - @Override - public void onSleepTimerReset() { - - } - - @Override public WidgetUpdater.WidgetState requestWidgetState() { return null; } @@ -234,21 +221,6 @@ public class PlaybackServiceTaskManagerTest { } @Override - public void onSleepTimerAlmostExpired(long timeLeft) { - - } - - @Override - public void onSleepTimerExpired() { - - } - - @Override - public void onSleepTimerReset() { - - } - - @Override public WidgetUpdater.WidgetState requestWidgetState() { countDownLatch.countDown(); return null; @@ -325,42 +297,20 @@ public class PlaybackServiceTaskManagerTest { final long TIME = 2000; final long TIMEOUT = 2 * TIME; final CountDownLatch countDownLatch = new CountDownLatch(1); - PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, new PlaybackServiceTaskManager.PSTMCallback() { - @Override - public void positionSaverTick() { - - } - - @Override - public void onSleepTimerAlmostExpired(long timeLeft) { - - } - - @Override - public void onSleepTimerExpired() { + Object timerReceiver = new Object() { + @Subscribe + public void sleepTimerUpdate(SleepTimerUpdatedEvent event) { if (countDownLatch.getCount() == 0) { fail(); } countDownLatch.countDown(); } - - @Override - public void onSleepTimerReset() { - - } - - @Override - public WidgetUpdater.WidgetState requestWidgetState() { - return null; - } - - @Override - public void onChapterLoaded(Playable media) { - - } - }); + }; + EventBus.getDefault().register(timerReceiver); + PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM); pstm.setSleepTimer(TIME); countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS); + EventBus.getDefault().unregister(timerReceiver); pstm.shutdown(); } @@ -368,44 +318,26 @@ public class PlaybackServiceTaskManagerTest { @UiThreadTest public void testDisableSleepTimer() throws InterruptedException { final Context c = InstrumentationRegistry.getInstrumentation().getTargetContext(); - final long TIME = 1000; + final long TIME = 5000; final long TIMEOUT = 2 * TIME; final CountDownLatch countDownLatch = new CountDownLatch(1); - PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, new PlaybackServiceTaskManager.PSTMCallback() { - @Override - public void positionSaverTick() { - - } - - @Override - public void onSleepTimerAlmostExpired(long timeLeft) { - - } - - @Override - public void onSleepTimerExpired() { - fail("Sleeptimer expired"); - } - - @Override - public void onSleepTimerReset() { - - } - - @Override - public WidgetUpdater.WidgetState requestWidgetState() { - return null; - } - - @Override - public void onChapterLoaded(Playable media) { - + Object timerReceiver = new Object() { + @Subscribe + public void sleepTimerUpdate(SleepTimerUpdatedEvent event) { + if (event.isOver()) { + countDownLatch.countDown(); + } else if (event.getTimeLeft() == 1) { + fail("Arrived at 1 but should have been cancelled"); + } } - }); + }; + PlaybackServiceTaskManager pstm = new PlaybackServiceTaskManager(c, defaultPSTM); + EventBus.getDefault().register(timerReceiver); pstm.setSleepTimer(TIME); pstm.disableSleepTimer(); assertFalse(countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS)); pstm.shutdown(); + EventBus.getDefault().unregister(timerReceiver); } @Test @@ -436,21 +368,6 @@ public class PlaybackServiceTaskManagerTest { } @Override - public void onSleepTimerAlmostExpired(long timeLeft) { - - } - - @Override - public void onSleepTimerExpired() { - - } - - @Override - public void onSleepTimerReset() { - - } - - @Override public WidgetUpdater.WidgetState requestWidgetState() { return null; } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java index c71bff357..74414240f 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -126,8 +126,9 @@ public class PreferencesTest { clickPreference(R.string.user_interface_label); String[] buttons = res.getStringArray(R.array.compact_notification_buttons_options); clickPreference(R.string.pref_compact_notification_buttons_title); - // First uncheck checkbox - onView(withText(buttons[2])).perform(click()); + // First uncheck checkboxes + onView(withText(buttons[0])).perform(click()); + onView(withText(buttons[1])).perform(click()); // Now try to check all checkboxes onView(withText(buttons[0])).perform(click()); diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java index b25f957d3..eedb2d9de 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java @@ -2,8 +2,8 @@ package de.test.antennapod.ui; import android.content.Context; import android.util.Log; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.QueueEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.QueueEvent; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; diff --git a/app/src/androidTest/java/de/test/antennapod/util/event/FeedItemEventListener.java b/app/src/androidTest/java/de/test/antennapod/util/event/FeedItemEventListener.java index 601bba853..7e8fc1205 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/event/FeedItemEventListener.java +++ b/app/src/androidTest/java/de/test/antennapod/util/event/FeedItemEventListener.java @@ -8,7 +8,7 @@ import org.greenrobot.eventbus.Subscribe; import java.util.ArrayList; import java.util.List; -import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedItemEvent; import io.reactivex.functions.Consumer; /** diff --git a/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java deleted file mode 100644 index 98d506f65..000000000 --- a/app/src/free/java/de/danoeh/antennapod/activity/CastEnabledActivity.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.danoeh.antennapod.activity; - -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 - * network. - */ -public abstract class CastEnabledActivity extends AppCompatActivity { - public static final String TAG = "CastEnabledActivity"; - - public final void requestCastButton(Menu menu) { - // no-op - } -} diff --git a/app/src/free/java/de/danoeh/antennapod/config/CastCallbackImpl.java b/app/src/free/java/de/danoeh/antennapod/config/CastCallbackImpl.java deleted file mode 100644 index fb23dfa1a..000000000 --- a/app/src/free/java/de/danoeh/antennapod/config/CastCallbackImpl.java +++ /dev/null @@ -1,7 +0,0 @@ -package de.danoeh.antennapod.config; - -import de.danoeh.antennapod.core.CastCallbacks; - -class CastCallbackImpl implements CastCallbacks { - -} diff --git a/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java b/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java deleted file mode 100644 index e096f883f..000000000 --- a/app/src/free/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java +++ /dev/null @@ -1,14 +0,0 @@ -package de.danoeh.antennapod.preferences; - -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.fragment.preferences.PlaybackPreferencesFragment; - -/** - * Implements functions from PreferenceController that are flavor dependent. - */ -public class PreferenceControllerFlavorHelper { - - public static void setupFlavoredUI(PlaybackPreferencesFragment ui) { - ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setEnabled(false); - } -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 47648f9d3..0f8242e63 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,7 +40,8 @@ android:supportsRtl="true" android:logo="@mipmap/ic_launcher" android:resizeableActivity="true" - android:allowAudioPlaybackCapture="true"> + android:allowAudioPlaybackCapture="true" + android:networkSecurityConfig="@xml/network_security_config"> <meta-data android:name="android.webkit.WebView.MetricsOptOut" @@ -53,6 +54,9 @@ <meta-data android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAI3a05VToCTlqBymJrbFGaKQMvF-bBAuLsOdavBA"/> + <meta-data + android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" + android:value="de.danoeh.antennapod.playback.cast.CastOptionsProvider" /> <!-- Version < 3.0. DeX Mode and Screen Mirroring support --> <meta-data android:name="com.samsung.android.keepalive.density" android:value="true"/> @@ -65,15 +69,13 @@ android:configChanges="keyboardHidden|orientation|screenSize" android:exported="true"> <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> + <action android:name="android.intent.action.MAIN" /> + <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" /> + <action android:name="android.intent.action.MUSIC_PLAYER" /> - <intent-filter> - <action android:name= - "android.media.action.MEDIA_PLAY_FROM_SEARCH" /> - <category android:name= - "android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + <category android:name="android.intent.category.APP_MUSIC" /> </intent-filter> <meta-data @@ -98,13 +100,6 @@ android:host="antennapod.org" android:pathPrefix="/deeplink/main" android:scheme="https" /> - </intent-filter> - <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:host="antennapod.org" android:pathPrefix="/deeplink/search" @@ -144,11 +139,7 @@ android:exported="true"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> - </intent-filter> - <intent-filter> <action android:name="de.danoeh.antennapod.FORCE_WIDGET_UPDATE"/> - </intent-filter> - <intent-filter> <action android:name="de.danoeh.antennapod.STOP_WIDGET_UPDATE"/> </intent-filter> <meta-data @@ -165,6 +156,7 @@ android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW"/> + <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> @@ -172,27 +164,15 @@ <data android:mimeType="text/xml"/> <data android:mimeType="text/x-opml"/> <data android:mimeType="application/xml"/> - <data android:mimeType="application/octet-stream"/> <data android:scheme="file"/> <data android:scheme="content"/> - - <data android:host="*"/> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.SEND"/> - - <category android:name="android.intent.category.DEFAULT"/> - <category android:name="android.intent.category.BROWSABLE"/> - - <data android:mimeType="text/xml"/> - <data android:mimeType="text/plain"/> - <data android:mimeType="text/x-opml"/> - <data android:mimeType="application/xml"/> - <data android:mimeType="application/octet-stream"/> - <data android:scheme="http"/> <data android:scheme="https"/> + + <data android:host="*"/> + <data android:pathPattern=".*.xml" /> + <data android:pathPattern=".*.opml" /> </intent-filter> </activity> <activity @@ -315,6 +295,18 @@ </intent-filter> <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:pathPattern="/.*/podcast/.*" /> + <data android:host="podcasts.apple.com" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + </intent-filter> + + <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> @@ -324,6 +316,16 @@ </activity> + <activity android:name=".activity.SelectSubscriptionActivity" + android:label="@string/shortcut_subscription_label" + android:icon="@drawable/ic_folder_shortcut" + android:theme="@style/Theme.AntennaPod.Dark.Translucent" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.CREATE_SHORTCUT" /> + </intent-filter> + </activity> + <receiver android:name=".receiver.ConnectivityActionReceiver" android:exported="true"> diff --git a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java index aa59e4e96..f7c96a93a 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java @@ -7,7 +7,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.util.Log; import com.google.android.material.snackbar.Snackbar; @@ -103,22 +102,21 @@ public class BugReportActivity extends AppCompatActivity { Runtime.getRuntime().exec(cmd); //share file try { - Intent i = new Intent(Intent.ACTION_SEND); - i.setType("text/*"); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/*"); String authString = getString(de.danoeh.antennapod.core.R.string.provider_authority); Uri fileUri = FileProvider.getUriForFile(this, authString, filename); - i.putExtra(Intent.EXTRA_STREAM, fileUri); - i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - PackageManager pm = getPackageManager(); - List<ResolveInfo> resInfos = pm.queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfos) { - String packageName = resolveInfo.activityInfo.packageName; - grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - } + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); String chooserTitle = getString(de.danoeh.antennapod.core.R.string.share_file_label); - startActivity(Intent.createChooser(i, chooserTitle)); + Intent chooser = Intent.createChooser(intent, chooserTitle); + List<ResolveInfo> resInfos = getPackageManager() + .queryIntentActivities(chooser, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfos) { + String packageName = resolveInfo.activityInfo.packageName; + grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + startActivity(chooser); } catch (Exception e) { e.printStackTrace(); int strResId = R.string.log_file_share_exception; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java index f07ad6ad5..7dc760e76 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -38,6 +38,7 @@ import com.bumptech.glide.Glide; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.playback.cast.CastEnabledActivity; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.Validate; import org.greenrobot.eventbus.EventBus; @@ -45,7 +46,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.MessageEvent; +import de.danoeh.antennapod.event.MessageEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.service.playback.PlaybackService; diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index 4dca1fda7..9148a9949 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -6,7 +6,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.LightingColorFilter; -import android.os.Build; import android.os.Bundle; import android.text.Spannable; import android.text.SpannableString; @@ -19,6 +18,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; @@ -31,8 +31,8 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.FastBlurTransformation; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; @@ -60,7 +60,6 @@ import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.discovery.PodcastSearcherRegistry; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedPreferences; -import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.model.playback.RemoteMedia; import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException; import io.reactivex.Maybe; @@ -101,6 +100,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { private Feed feed; private String selectedDownloadUrl; private Downloader downloader; + private String username = null; + private String password = null; private boolean isPaused; private boolean didPressSubscribe = false; @@ -144,12 +145,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (feedUrl.contains("subscribeonandroid.com")) { feedUrl = feedUrl.replaceFirst("((www.)?(subscribeonandroid.com/))", ""); } - if (savedInstanceState == null) { - lookupUrlAndDownload(feedUrl, null, null); - } else { - lookupUrlAndDownload(feedUrl, savedInstanceState.getString("username"), - savedInstanceState.getString("password")); + if (savedInstanceState != null) { + username = savedInstanceState.getString("username"); + password = savedInstanceState.getString("password"); } + lookupUrlAndDownload(feedUrl); } } @@ -210,10 +210,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - if (feed != null && feed.getPreferences() != null) { - outState.putString("username", feed.getPreferences().getUsername()); - outState.putString("password", feed.getPreferences().getPassword()); - } + outState.putString("username", username); + outState.putString("password", password); } private void resetIntent(String url) { @@ -242,32 +240,23 @@ public class OnlineFeedViewActivity extends AppCompatActivity { return super.onOptionsItemSelected(item); } - private void lookupUrlAndDownload(String url, String username, String password) { + private void lookupUrlAndDownload(String url) { download = PodcastSearcherRegistry.lookupUrl(url) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) - .subscribe(lookedUpUrl -> startFeedDownload(lookedUpUrl, username, password), + .subscribe(this::startFeedDownload, error -> { showNoPodcastFoundError(); Log.e(TAG, Log.getStackTraceString(error)); }); } - private void startFeedDownload(String url, String username, String password) { + private void startFeedDownload(String url) { Log.d(TAG, "Starting feed download"); url = URLChecker.prepareURL(url); feed = new Feed(url, null); - if (username != null && password != null) { - feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, - VolumeAdaptionSetting.OFF, username, password)); - } - String fileUrl; - try { - fileUrl = DownloadRequester.getInstance().getDownloadPathForFeed(feed).getAbsolutePath(); - } catch (DownloadRequestException e) { - e.printStackTrace(); - fileUrl = new File(getCacheDir(), FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); - } + String fileUrl = new File(getExternalCacheDir(), + FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); feed.setFile_url(fileUrl); final DownloadRequest request = new DownloadRequest(feed.getFile_url(), feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED, username, password, @@ -293,6 +282,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { parseFeed(); } else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { if (!isFinishing() && !isPaused) { + if (username != null && password != null) { + Toast.makeText(this, R.string.download_error_unauthorized, Toast.LENGTH_LONG).show(); + } dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this, R.string.authentication_notification_title, downloader.getDownloadRequest().getSource()).create(); @@ -458,8 +450,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { final int MAX_LINES_COLLAPSED = 10; description.setMaxLines(MAX_LINES_COLLAPSED); description.setOnClickListener(v -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && description.getMaxLines() > MAX_LINES_COLLAPSED) { + if (description.getMaxLines() > MAX_LINES_COLLAPSED) { description.setMaxLines(MAX_LINES_COLLAPSED); } else { description.setMaxLines(2000); @@ -642,21 +633,17 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (urls.size() == 1) { // Skip dialog and display the item directly resetIntent(urls.get(0)); - startFeedDownload(urls.get(0), null, null); + startFeedDownload(urls.get(0)); return true; } - final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this, R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles); + final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this, + R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles); DialogInterface.OnClickListener onClickListener = (dialog, which) -> { String selectedUrl = urls.get(which); dialog.dismiss(); resetIntent(selectedUrl); - FeedPreferences prefs = feed.getPreferences(); - if(prefs != null) { - startFeedDownload(selectedUrl, prefs.getUsername(), prefs.getPassword()); - } else { - startFeedDownload(selectedUrl, null, null); - } + startFeedDownload(selectedUrl); }; AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this) @@ -679,7 +666,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { private final String feedUrl; FeedViewAuthenticationDialog(Context context, int titleRes, String feedUrl) { - super(context, titleRes, true, null, null); + super(context, titleRes, true, username, password); this.feedUrl = feedUrl; } @@ -691,7 +678,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { @Override protected void onConfirmed(String username, String password) { - startFeedDownload(feedUrl, username, password); + OnlineFeedViewActivity.this.username = username; + OnlineFeedViewActivity.this.password = password; + startFeedDownload(feedUrl); } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java index a6810715c..3d0c9d113 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java @@ -1,5 +1,6 @@ package de.danoeh.antennapod.activity; +import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -15,7 +16,9 @@ import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; -import androidx.annotation.NonNull; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -35,7 +38,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import org.apache.commons.io.ByteOrderMark; import org.apache.commons.io.input.BOMInputStream; -import org.apache.commons.lang3.ArrayUtils; import java.io.InputStream; import java.io.InputStreamReader; @@ -48,7 +50,6 @@ import java.util.List; * */ public class OpmlImportActivity extends AppCompatActivity { private static final String TAG = "OpmlImportBaseActivity"; - private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 5; @Nullable private Uri uri; OpmlSelectionBinding viewBinding; private ArrayAdapter<String> listAdapter; @@ -198,27 +199,23 @@ public class OpmlImportActivity extends AppCompatActivity { } private void requestPermission() { - String[] permissions = { android.Manifest.permission.READ_EXTERNAL_STORAGE }; - ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_READ_EXTERNAL_STORAGE); + requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE); } - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, - @NonNull int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode != PERMISSION_REQUEST_READ_EXTERNAL_STORAGE) { - return; - } - if (grantResults.length > 0 && ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED)) { - startImport(); - } else { - new AlertDialog.Builder(this) - .setMessage(R.string.opml_import_ask_read_permission) - .setPositiveButton(android.R.string.ok, (dialog, which) -> requestPermission()) - .setNegativeButton(R.string.cancel_label, (dialog, which) -> finish()) - .show(); - } - } + private final ActivityResultLauncher<String> requestPermissionLauncher = + registerForActivityResult(new RequestPermission(), isGranted -> { + if (isGranted) { + startImport(); + } else { + new AlertDialog.Builder(this) + .setMessage(R.string.opml_import_ask_read_permission) + .setPositiveButton(android.R.string.ok, (dialog, which) -> + requestPermission()) + .setNegativeButton(R.string.cancel_label, (dialog, which) -> + finish()) + .show(); + } + }); /** Starts the import process. */ private void startImport() { diff --git a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java index 600204554..1fc16ab32 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/PreferenceActivity.java @@ -6,6 +6,7 @@ import android.os.Bundle; import android.provider.Settings; import android.view.Menu; import android.view.MenuItem; + import android.view.View; import android.view.inputmethod.InputMethodManager; @@ -21,13 +22,13 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.databinding.SettingsActivityBinding; import de.danoeh.antennapod.fragment.preferences.AutoDownloadPreferencesFragment; -import de.danoeh.antennapod.fragment.preferences.GpodderPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.ImportExportPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.MainPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.NetworkPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.NotificationPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.PlaybackPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.StoragePreferencesFragment; +import de.danoeh.antennapod.fragment.preferences.synchronization.SynchronizationPreferencesFragment; import de.danoeh.antennapod.fragment.preferences.SwipePreferencesFragment; import de.danoeh.antennapod.fragment.preferences.UserInterfacePreferencesFragment; @@ -76,8 +77,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe prefFragment = new ImportExportPreferencesFragment(); } else if (screen == R.xml.preferences_autodownload) { prefFragment = new AutoDownloadPreferencesFragment(); - } else if (screen == R.xml.preferences_gpodder) { - prefFragment = new GpodderPreferencesFragment(); + } else if (screen == R.xml.preferences_synchronization) { + prefFragment = new SynchronizationPreferencesFragment(); } else if (screen == R.xml.preferences_playback) { prefFragment = new PlaybackPreferencesFragment(); } else if (screen == R.xml.preferences_notifications) { @@ -101,8 +102,8 @@ public class PreferenceActivity extends AppCompatActivity implements SearchPrefe return R.string.import_export_pref; } else if (preferences == R.xml.preferences_user_interface) { return R.string.user_interface_label; - } else if (preferences == R.xml.preferences_gpodder) { - return R.string.gpodnet_main_label; + } else if (preferences == R.xml.preferences_synchronization) { + return R.string.synchronization_pref; } else if (preferences == R.xml.preferences_notifications) { return R.string.notification_pref_fragment; } else if (preferences == R.xml.feed_settings) { diff --git a/app/src/main/java/de/danoeh/antennapod/activity/SelectSubscriptionActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/SelectSubscriptionActivity.java new file mode 100644 index 000000000..4ffed949e --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/activity/SelectSubscriptionActivity.java @@ -0,0 +1,162 @@ +package de.danoeh.antennapod.activity; + +import static de.danoeh.antennapod.activity.MainActivity.EXTRA_FEED_ID; + +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.util.Log; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.Target; + +import java.util.ArrayList; +import java.util.List; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.NavDrawerData; +import de.danoeh.antennapod.databinding.SubscriptionSelectionActivityBinding; +import de.danoeh.antennapod.model.feed.Feed; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +public class SelectSubscriptionActivity extends AppCompatActivity { + + private static final String TAG = "SelectSubscription"; + + private Disposable disposable; + private volatile List<Feed> listItems; + + private SubscriptionSelectionActivityBinding viewBinding; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + setTheme(UserPreferences.getTranslucentTheme()); + super.onCreate(savedInstanceState); + + viewBinding = SubscriptionSelectionActivityBinding.inflate(getLayoutInflater()); + setContentView(viewBinding.getRoot()); + setSupportActionBar(viewBinding.toolbar); + setTitle(R.string.shortcut_select_subscription); + + viewBinding.transparentBackground.setOnClickListener(v -> finish()); + viewBinding.card.setOnClickListener(null); + + loadSubscriptions(); + + final Integer[] checkedPosition = new Integer[1]; + viewBinding.list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); + viewBinding.list.setOnItemClickListener((listView, view1, position, rowId) -> + checkedPosition[0] = position + ); + viewBinding.shortcutBtn.setOnClickListener(view -> { + if (checkedPosition[0] != null && Intent.ACTION_CREATE_SHORTCUT.equals( + getIntent().getAction())) { + getBitmapFromUrl(listItems.get(checkedPosition[0])); + } + }); + + } + + public List<Feed> getFeedItems(List<NavDrawerData.DrawerItem> items, List<Feed> result) { + for (NavDrawerData.DrawerItem item : items) { + if (item.type == NavDrawerData.DrawerItem.Type.TAG) { + getFeedItems(((NavDrawerData.TagDrawerItem) item).children, result); + } else { + Feed feed = ((NavDrawerData.FeedDrawerItem) item).feed; + if (!result.contains(feed)) { + result.add(feed); + } + } + } + return result; + } + + private void addShortcut(Feed feed, Bitmap bitmap) { + Intent intent = new Intent(this, MainActivity.class); + intent.setAction(Intent.ACTION_MAIN); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra(EXTRA_FEED_ID, feed.getId()); + String id = "subscription-" + feed.getId(); + IconCompat icon; + + if (bitmap != null) { + icon = IconCompat.createWithAdaptiveBitmap(bitmap); + } else { + icon = IconCompat.createWithResource(this, R.drawable.ic_folder_shortcut); + } + + ShortcutInfoCompat shortcut = new ShortcutInfoCompat.Builder(this, id) + .setShortLabel(feed.getTitle()) + .setLongLabel(feed.getFeedTitle()) + .setIntent(intent) + .setIcon(icon) + .build(); + + setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut)); + finish(); + } + + private void getBitmapFromUrl(Feed feed) { + int iconSize = (int) (128 * getResources().getDisplayMetrics().density); + Glide.with(this) + .asBitmap() + .load(feed.getImageUrl()) + .apply(new RequestOptions().override(iconSize, iconSize)) + .listener(new RequestListener<Bitmap>() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, + Target<Bitmap> target, boolean isFirstResource) { + addShortcut(feed, null); + return true; + } + + @Override + public boolean onResourceReady(Bitmap resource, Object model, + Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) { + addShortcut(feed, resource); + return true; + } + }).submit(); + } + + private void loadSubscriptions() { + if (disposable != null) { + disposable.dispose(); + } + disposable = Observable.fromCallable( + () -> { + NavDrawerData data = DBReader.getNavDrawerData(); + return getFeedItems(data.items, new ArrayList<>()); + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + result -> { + listItems = result; + ArrayList<String> titles = new ArrayList<>(); + for (Feed feed: result) { + titles.add(feed.getTitle()); + } + ArrayAdapter<String> adapter = new ArrayAdapter<>(this, + R.layout.simple_list_item_multiple_choice_on_start, titles); + viewBinding.list.setAdapter(adapter); + }, error -> Log.e(TAG, Log.getStackTraceString(error))); + } +}
\ No newline at end of file diff --git a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java index d436acf0d..4ff2a5775 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -36,11 +36,13 @@ import androidx.core.view.WindowCompat; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import com.bumptech.glide.Glide; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.ServiceEvent; +import de.danoeh.antennapod.event.playback.BufferUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.util.Converter; @@ -50,7 +52,6 @@ import de.danoeh.antennapod.core.util.ShareUtils; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.TimeSpeedConverter; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; -import de.danoeh.antennapod.core.util.playback.MediaPlayerError; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.databinding.VideoplayerActivityBinding; import de.danoeh.antennapod.dialog.PlaybackControlsDialog; @@ -60,6 +61,8 @@ import de.danoeh.antennapod.dialog.SleepTimerDialog; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.playback.Playable; +import de.danoeh.antennapod.playback.base.PlayerStatus; +import de.danoeh.antennapod.playback.cast.CastEnabledActivity; import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -193,40 +196,11 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar. } @Override - public void onBufferStart() { - viewBinding.progressBar.setVisibility(View.VISIBLE); - } - - @Override - public void onBufferEnd() { - viewBinding.progressBar.setVisibility(View.INVISIBLE); - } - - @Override - public void onBufferUpdate(float progress) { - viewBinding.sbPosition.setSecondaryProgress((int) (progress * viewBinding.sbPosition.getMax())); - } - - @Override - public void handleError(int code) { - final AlertDialog.Builder errorDialog = new AlertDialog.Builder(VideoplayerActivity.this); - errorDialog.setTitle(R.string.error_label); - errorDialog.setMessage(MediaPlayerError.getErrorString(VideoplayerActivity.this, code)); - errorDialog.setNeutralButton(android.R.string.ok, (dialog, which) -> finish()); - errorDialog.show(); - } - - @Override public void onReloadNotification(int code) { VideoplayerActivity.this.onReloadNotification(code); } @Override - public void onSleepTimerUpdate() { - supportInvalidateOptionsMenu(); - } - - @Override protected void updatePlayButtonShowsPlay(boolean showPlay) { viewBinding.playButton.setIsShowPlay(showPlay); } @@ -261,6 +235,26 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar. }; } + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void bufferUpdate(BufferUpdateEvent event) { + if (event.hasStarted()) { + viewBinding.progressBar.setVisibility(View.VISIBLE); + } else if (event.hasEnded()) { + viewBinding.progressBar.setVisibility(View.INVISIBLE); + } else { + viewBinding.sbPosition.setSecondaryProgress((int) (event.getProgress() * viewBinding.sbPosition.getMax())); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void sleepTimerUpdate(SleepTimerUpdatedEvent event) { + if (event.isCancelled() || event.wasJustEnabled()) { + supportInvalidateOptionsMenu(); + } + } + protected void loadMediaInfo() { Log.d(TAG, "loadMediaInfo()"); if (controller == null || controller.getMedia() == null) { @@ -544,12 +538,21 @@ public class VideoplayerActivity extends CastEnabledActivity implements SeekBar. } @Subscribe(threadMode = ThreadMode.MAIN) - public void onPlaybackServiceChanged(ServiceEvent event) { - if (event.action == ServiceEvent.Action.SERVICE_SHUT_DOWN) { + public void onPlaybackServiceChanged(PlaybackServiceEvent event) { + if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) { finish(); } } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onMediaPlayerError(PlayerErrorEvent event) { + final AlertDialog.Builder errorDialog = new AlertDialog.Builder(VideoplayerActivity.this); + errorDialog.setTitle(R.string.error_label); + errorDialog.setMessage(event.getMessage()); + errorDialog.setNeutralButton(android.R.string.ok, (dialog, which) -> finish()); + errorDialog.show(); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java index 3020aba43..674071294 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java @@ -1,21 +1,14 @@ package de.danoeh.antennapod.activity; -import android.Manifest; -import android.app.WallpaperManager; import android.appwidget.AppWidgetManager; import android.content.Intent; import android.content.SharedPreferences; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; -import android.os.Build; import android.os.Bundle; import android.view.View; import android.widget.CheckBox; -import android.widget.ImageView; import android.widget.SeekBar; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.receiver.PlayerWidget; @@ -51,7 +44,6 @@ public class WidgetConfigActivity extends AppCompatActivity { finish(); } - displayDeviceBackground(); opacityTextView = findViewById(R.id.widget_opacity_textView); opacitySeekBar = findViewById(R.id.widget_opacity_seekBar); widgetPreview = findViewById(R.id.widgetLayout); @@ -102,16 +94,6 @@ public class WidgetConfigActivity extends AppCompatActivity { widgetPreview.findViewById(R.id.butRew).setVisibility(ckRewind.isChecked() ? View.VISIBLE : View.GONE); } - private void displayDeviceBackground() { - int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE); - if (Build.VERSION.SDK_INT < 27 || permission == PackageManager.PERMISSION_GRANTED) { - final WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); - final Drawable wallpaperDrawable = wallpaperManager.getDrawable(); - ImageView background = findViewById(R.id.widget_config_background); - background.setImageDrawable(wallpaperDrawable); - } - } - private void confirmCreateWidget(View v) { int backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.getProgress()); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java index 2ab96e84d..5ddb6407c 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java @@ -95,7 +95,7 @@ public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> { holder.preview.setVisibility(View.GONE); holder.description.setTag(Boolean.FALSE); } else { - holder.description.setMaxLines(2000); + holder.description.setMaxLines(30); holder.description.setTag(Boolean.TRUE); holder.preview.setVisibility(item.getMedia() != null ? View.VISIBLE : View.GONE); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java index ff0311ab6..7854f7aa9 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java @@ -196,7 +196,7 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder> bindFeedView((NavDrawerData.FeedDrawerItem) item, (FeedHolder) holder); holder.itemView.setOnCreateContextMenuListener(itemAccess); } else { - bindFolderView((NavDrawerData.FolderDrawerItem) item, (FeedHolder) holder); + bindTagView((NavDrawerData.TagDrawerItem) item, (FeedHolder) holder); } } if (viewType != VIEW_TYPE_SECTION_DIVIDER) { @@ -327,16 +327,16 @@ public class NavListAdapter extends RecyclerView.Adapter<NavListAdapter.Holder> } } - private void bindFolderView(NavDrawerData.FolderDrawerItem folder, FeedHolder holder) { + private void bindTagView(NavDrawerData.TagDrawerItem tag, FeedHolder holder) { Activity context = activity.get(); if (context == null) { return; } - if (folder.isOpen) { + if (tag.isOpen) { holder.count.setVisibility(View.GONE); } Glide.with(context).clear(holder.image); - holder.image.setImageResource(R.drawable.ic_folder); + holder.image.setImageResource(R.drawable.ic_tag); holder.failure.setVisibility(View.GONE); } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java index 5fec5f063..26674b2b2 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java @@ -1,13 +1,12 @@ package de.danoeh.antennapod.adapter; -import android.content.Context; -import androidx.appcompat.app.AlertDialog; - +import androidx.fragment.app.Fragment; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateFormatter; +import de.danoeh.antennapod.fragment.FeedStatisticsDialogFragment; import de.danoeh.antennapod.view.PieChartView; import java.util.Date; @@ -18,10 +17,12 @@ import java.util.List; */ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { + private final Fragment fragment; boolean countAll = true; - public PlaybackStatisticsListAdapter(Context context) { - super(context); + public PlaybackStatisticsListAdapter(Fragment fragment) { + super(fragment.getContext()); + this.fragment = fragment; } public void setCountAll(boolean countAll) { @@ -60,16 +61,9 @@ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { holder.value.setText(Converter.shortLocalizedDuration(context, time)); holder.itemView.setOnClickListener(v -> { - AlertDialog.Builder dialog = new AlertDialog.Builder(context); - dialog.setTitle(statsItem.feed.getTitle()); - dialog.setMessage(context.getString(R.string.statistics_details_dialog, - countAll ? statsItem.episodesStartedIncludingMarked : statsItem.episodesStarted, - statsItem.episodes, Converter.shortLocalizedDuration(context, - countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed), - Converter.shortLocalizedDuration(context, statsItem.time))); - dialog.setPositiveButton(android.R.string.ok, null); - dialog.show(); + FeedStatisticsDialogFragment yourDialogFragment = FeedStatisticsDialogFragment.newInstance( + statsItem.feed.getId(), statsItem.feed.getTitle()); + yourDialogFragment.show(fragment.getChildFragmentManager().beginTransaction(), "DialogFragment"); }); } - } diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java index 73f67d016..b637eb31d 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java @@ -219,7 +219,7 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription .load(); } else { new CoverLoader(mainActivityRef.get()) - .withResource(R.drawable.ic_folder) + .withResource(R.drawable.ic_tag) .withPlaceholderView(feedTitle, true) .withCoverView(imageView) .load(); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java index dedf8e5e6..a2b0e98c3 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/CancelDownloadActionButton.java @@ -34,7 +34,7 @@ public class CancelDownloadActionButton extends ItemActionButton { FeedMedia media = item.getMedia(); DownloadRequester.getInstance().cancelDownload(context, media); if (UserPreferences.isEnableAutodownload()) { - item.setAutoDownload(false); + item.disableAutoDownload(); DBWriter.setFeedItem(item); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java index a45eb5199..1f4f657b1 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -15,6 +15,5 @@ class ClientConfigurator { ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME; ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl(); ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); - ClientConfig.castCallbacks = new CastCallbackImpl(); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java index 938bb5931..590b7c897 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.config; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import de.danoeh.antennapod.R; @@ -24,7 +25,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); return PendingIntent.getActivity(context, - R.id.pending_intent_download_service_notification, intent, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_download_service_notification, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override @@ -33,7 +35,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { activityIntent.setAction("request" + request.getFeedfileId()); activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, request); return PendingIntent.getActivity(context.getApplicationContext(), - R.id.pending_intent_download_service_auth, activityIntent, PendingIntent.FLAG_ONE_SHOT); + R.id.pending_intent_download_service_auth, activityIntent, + PendingIntent.FLAG_ONE_SHOT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override @@ -43,15 +46,15 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { Bundle args = new Bundle(); args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); - return PendingIntent.getActivity(context, R.id.pending_intent_download_service_report, - intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(context, R.id.pending_intent_download_service_report, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override public PendingIntent getAutoDownloadReportNotificationContentIntent(Context context) { Intent intent = new Intent(context, MainActivity.class); intent.putExtra(MainActivity.EXTRA_FRAGMENT_TAG, QueueFragment.TAG); - return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report, - intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java index ee19a0339..595f37e40 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodeFilterDialog.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.dialog; import android.content.Context; import android.view.View; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.RadioButton; @@ -14,7 +15,6 @@ import de.danoeh.antennapod.model.feed.FeedFilter; * Displays a dialog with a text box for filtering episodes and two radio buttons for exclusion/inclusion */ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { - private final FeedFilter initialFilter; public EpisodeFilterDialog(Context context, FeedFilter filter) { @@ -26,8 +26,10 @@ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { setView(rootView); final EditText etxtEpisodeFilterText = rootView.findViewById(R.id.etxtEpisodeFilterText); + final EditText etxtEpisodeFilterDurationText = rootView.findViewById(R.id.etxtEpisodeFilterDurationText); final RadioButton radioInclude = rootView.findViewById(R.id.radio_filter_include); final RadioButton radioExclude = rootView.findViewById(R.id.radio_filter_exclude); + final CheckBox checkboxDuration = rootView.findViewById(R.id.checkbox_filter_duration); if (initialFilter.includeOnly()) { radioInclude.setChecked(true); @@ -40,18 +42,31 @@ public abstract class EpisodeFilterDialog extends AlertDialog.Builder { radioInclude.setChecked(false); etxtEpisodeFilterText.setText(""); } + if (initialFilter.hasMinimalDurationFilter()) { + checkboxDuration.setChecked(true); + // Store minimal duration in seconds, show in minutes + etxtEpisodeFilterDurationText.setText(String.valueOf(initialFilter.getMinimalDurationFilter() / 60)); + } setNegativeButton(R.string.cancel_label, null); setPositiveButton(R.string.confirm_label, (dialog, which) -> { String includeString = ""; String excludeString = ""; + int minimalDuration = -1; if (radioInclude.isChecked()) { includeString = etxtEpisodeFilterText.getText().toString(); } else { excludeString = etxtEpisodeFilterText.getText().toString(); } - - onConfirmed(new FeedFilter(includeString, excludeString)); + if (checkboxDuration.isChecked()) { + try { + // Store minimal duration in seconds + minimalDuration = Integer.parseInt(etxtEpisodeFilterDurationText.getText().toString()) * 60; + } catch (NumberFormatException e) { + // Do not change anything on error + } + } + onConfirmed(new FeedFilter(includeString, excludeString, minimalDuration)); } ); } diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/FeedSortDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/FeedSortDialog.java index 96d1b9b67..b89d05f88 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FeedSortDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/FeedSortDialog.java @@ -10,7 +10,7 @@ import java.util.Arrays; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; public class FeedSortDialog { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java index 3186cbe2e..5cc1f99c6 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -12,9 +12,13 @@ import android.widget.Button; import android.widget.CheckBox; import android.widget.TextView; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.view.PlaybackSpeedSeekBar; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; import java.util.List; import java.util.Locale; @@ -22,6 +26,8 @@ import java.util.Locale; public class PlaybackControlsDialog extends DialogFragment { private PlaybackController controller; private AlertDialog dialog; + private PlaybackSpeedSeekBar speedSeekBar; + private TextView txtvPlaybackSpeed; public static PlaybackControlsDialog newInstance() { Bundle arguments = new Bundle(); @@ -42,10 +48,12 @@ public class PlaybackControlsDialog extends DialogFragment { public void loadMediaInfo() { setupUi(); setupAudioTracks(); + updateSpeed(new SpeedChangedEvent(getCurrentPlaybackSpeedMultiplier())); } }; controller.init(); setupUi(); + EventBus.getDefault().register(this); } @Override @@ -53,6 +61,7 @@ public class PlaybackControlsDialog extends DialogFragment { super.onStop(); controller.release(); controller = null; + EventBus.getDefault().unregister(this); } @NonNull @@ -66,12 +75,14 @@ public class PlaybackControlsDialog extends DialogFragment { } private void setupUi() { - final TextView txtvPlaybackSpeed = dialog.findViewById(R.id.txtvPlaybackSpeed); - - PlaybackSpeedSeekBar speedSeekBar = dialog.findViewById(R.id.speed_seek_bar); - speedSeekBar.setController(controller); - speedSeekBar.setProgressChangedListener(speed - -> txtvPlaybackSpeed.setText(String.format(Locale.getDefault(), "%.2fx", speed))); + txtvPlaybackSpeed = dialog.findViewById(R.id.txtvPlaybackSpeed); + speedSeekBar = dialog.findViewById(R.id.speed_seek_bar); + speedSeekBar.setProgressChangedListener(speed -> { + if (controller != null) { + controller.setPlaybackSpeed(speed); + } + }); + updateSpeed(new SpeedChangedEvent(controller.getCurrentPlaybackSpeedMultiplier())); final CheckBox stereoToMono = dialog.findViewById(R.id.stereo_to_mono); stereoToMono.setChecked(UserPreferences.stereoToMono()); @@ -100,6 +111,12 @@ public class PlaybackControlsDialog extends DialogFragment { }); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void updateSpeed(SpeedChangedEvent event) { + txtvPlaybackSpeed.setText(String.format(Locale.getDefault(), "%.2fx", event.getNewSpeed())); + speedSeekBar.updateSpeed(event.getNewSpeed()); + } + private void setupAudioTracks() { List<String> audioTracks = controller.getAudioTracks(); int selectedAudioTrack = controller.getSelectedAudioTrack(); diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java index 13258b4ec..ad2ed3499 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/ProxyDialog.java @@ -67,6 +67,7 @@ public class ProxyDialog { .setView(content) .setNegativeButton(R.string.cancel_label, null) .setPositiveButton(R.string.proxy_test_label, null) + .setNeutralButton(R.string.reset, null) .show(); // To prevent cancelling the dialog on button click dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener((view) -> { @@ -75,36 +76,19 @@ public class ProxyDialog { test(); return; } - String type = (String) spType.getSelectedItem(); - ProxyConfig proxy; - if (Proxy.Type.valueOf(type) == Proxy.Type.DIRECT) { - proxy = ProxyConfig.direct(); - } else { - String host = etHost.getText().toString(); - String port = etPort.getText().toString(); - String username = etUsername.getText().toString(); - if (TextUtils.isEmpty(username)) { - username = null; - } - String password = etPassword.getText().toString(); - if (TextUtils.isEmpty(password)) { - password = null; - } - int portValue = 0; - if (!TextUtils.isEmpty(port)) { - portValue = Integer.parseInt(port); - } - if (Proxy.Type.valueOf(type) == Proxy.Type.SOCKS) { - proxy = ProxyConfig.socks(host, portValue, username, password); - } else { - proxy = ProxyConfig.http(host, portValue, username, password); - } - } - UserPreferences.setProxyConfig(proxy); + setProxyConfig(); AntennapodHttpClient.reinit(); dialog.dismiss(); }); + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener((view) -> { + etHost.getText().clear(); + etPort.getText().clear(); + etUsername.getText().clear(); + etPassword.getText().clear(); + setProxyConfig(); + }); + List<String> types = new ArrayList<>(); types.add(Proxy.Type.DIRECT.name()); types.add(Proxy.Type.HTTP.name()); @@ -144,6 +128,11 @@ public class ProxyDialog { spType.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + if (position == 0) { + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setVisibility(View.GONE); + } else { + dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setVisibility(View.VISIBLE); + } enableSettings(position > 0); setTestRequired(position > 0); } @@ -158,6 +147,35 @@ public class ProxyDialog { return dialog; } + private void setProxyConfig() { + String type = (String) spType.getSelectedItem(); + ProxyConfig proxy; + if (Proxy.Type.valueOf(type) == Proxy.Type.DIRECT) { + proxy = ProxyConfig.direct(); + } else { + String host = etHost.getText().toString(); + String port = etPort.getText().toString(); + String username = etUsername.getText().toString(); + if (TextUtils.isEmpty(username)) { + username = null; + } + String password = etPassword.getText().toString(); + if (TextUtils.isEmpty(password)) { + password = null; + } + int portValue = 0; + if (!TextUtils.isEmpty(port)) { + portValue = Integer.parseInt(port); + } + if (Proxy.Type.valueOf(type) == Proxy.Type.SOCKS) { + proxy = ProxyConfig.socks(host, portValue, username, password); + } else { + proxy = ProxyConfig.http(host, portValue, username, password); + } + } + UserPreferences.setProxyConfig(proxy); + } + private final TextWatcher requireTestOnChange = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java index 9fcf8be69..23c032248 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java @@ -19,18 +19,18 @@ import io.reactivex.schedulers.Schedulers; public class RemoveFeedDialog { private static final String TAG = "RemoveFeedDialog"; - public static void show(Context context, Feed feed, Runnable onSuccess) { + public static void show(Context context, Feed feed) { List<Feed> feeds = Collections.singletonList(feed); String message = getMessageId(context, feeds); - showDialog(context, feeds, message, onSuccess); + showDialog(context, feeds, message); } - public static void show(Context context, List<Feed> feeds, Runnable onSuccess) { + public static void show(Context context, List<Feed> feeds) { String message = getMessageId(context, feeds); - showDialog(context, feeds, message, onSuccess); + showDialog(context, feeds, message); } - private static void showDialog(Context context, List<Feed> feeds, String message, Runnable onSuccess) { + private static void showDialog(Context context, List<Feed> feeds, String message) { ConfirmationDialog dialog = new ConfirmationDialog(context, R.string.remove_feed_label, message) { @Override public void onConfirmButtonPressed(DialogInterface clickedDialog) { @@ -42,20 +42,16 @@ public class RemoveFeedDialog { progressDialog.setCancelable(false); progressDialog.show(); - Completable.fromCallable(() -> { + Completable.fromAction(() -> { for (Feed feed : feeds) { DBWriter.deleteFeed(context, feed.getId()).get(); } - return null; }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( () -> { Log.d(TAG, "Feed(s) deleted"); - if (onSuccess != null) { - onSuccess.run(); - } progressDialog.dismiss(); }, error -> { Log.e(TAG, Log.getStackTraceString(error)); diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java index 691bd65e8..764940e06 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java @@ -18,20 +18,17 @@ import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.playback.PlaybackController; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; - -import java.util.concurrent.TimeUnit; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; public class SleepTimerDialog extends DialogFragment { private PlaybackController controller; - private Disposable timeUpdater; - private EditText etxtTime; private Spinner spTimeUnit; private LinearLayout timeSetup; @@ -47,19 +44,11 @@ public class SleepTimerDialog extends DialogFragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public void onSleepTimerUpdate() { - updateTime(); - } - - @Override public void loadMediaInfo() { - updateTime(); } }; controller.init(); - timeUpdater = Observable.interval(1, TimeUnit.SECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(tick -> updateTime()); + EventBus.getDefault().register(this); } @Override @@ -68,9 +57,7 @@ public class SleepTimerDialog extends DialogFragment { if (controller != null) { controller.release(); } - if (timeUpdater != null) { - timeUpdater.dispose(); - } + EventBus.getDefault().unregister(this); } @NonNull @@ -170,13 +157,12 @@ public class SleepTimerDialog extends DialogFragment { return builder.create(); } - private void updateTime() { - if (controller == null) { - return; - } - timeSetup.setVisibility(controller.sleepTimerActive() ? View.GONE : View.VISIBLE); - timeDisplay.setVisibility(controller.sleepTimerActive() ? View.VISIBLE : View.GONE); - time.setText(Converter.getDurationStringLong((int) controller.getSleepTimerTimeLeft())); + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void timerUpdated(SleepTimerUpdatedEvent event) { + timeDisplay.setVisibility(event.isOver() || event.isCancelled() ? View.GONE : View.VISIBLE); + timeSetup.setVisibility(event.isOver() || event.isCancelled() ? View.VISIBLE : View.GONE); + time.setText(Converter.getDurationStringLong((int) event.getTimeLeft())); } private void closeKeyboard(View content) { diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java index 29172bb5e..9e524188f 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java @@ -16,7 +16,7 @@ import java.util.HashSet; import java.util.Set; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.SubscriptionsFilter; import de.danoeh.antennapod.core.feed.SubscriptionsFilterGroup; import de.danoeh.antennapod.core.preferences.UserPreferences; diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java index 8ef01590f..8f5f1b802 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/TagSettingsDialog.java @@ -27,7 +27,9 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class TagSettingsDialog extends DialogFragment { public static final String TAG = "TagSettingsDialog"; @@ -36,10 +38,10 @@ public class TagSettingsDialog extends DialogFragment { private EditTagsDialogBinding viewBinding; private TagSelectionAdapter adapter; - public static TagSettingsDialog newInstance(FeedPreferences preferences) { + public static TagSettingsDialog newInstance(List<FeedPreferences> preferencesList) { TagSettingsDialog fragment = new TagSettingsDialog(); Bundle args = new Bundle(); - args.putSerializable(ARG_FEED_PREFERENCES, preferences); + args.putSerializable(ARG_FEED_PREFERENCES, new ArrayList<>(preferencesList)); fragment.setArguments(args); return fragment; } @@ -47,8 +49,14 @@ public class TagSettingsDialog extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - FeedPreferences preferences = (FeedPreferences) getArguments().getSerializable(ARG_FEED_PREFERENCES); - displayedTags = new ArrayList<>(preferences.getTags()); + ArrayList<FeedPreferences> feedPreferencesList = + (ArrayList<FeedPreferences>) getArguments().getSerializable(ARG_FEED_PREFERENCES); + Set<String> commonTags = new HashSet<>(feedPreferencesList.get(0).getTags()); + + for (FeedPreferences preference : feedPreferencesList) { + commonTags.retainAll(preference.getTags()); + } + displayedTags = new ArrayList<>(commonTags); displayedTags.remove(FeedPreferences.TAG_ROOT); viewBinding = EditTagsDialogBinding.inflate(getLayoutInflater()); @@ -57,7 +65,7 @@ public class TagSettingsDialog extends DialogFragment { adapter = new TagSelectionAdapter(); adapter.setHasStableIds(true); viewBinding.tagsRecycler.setAdapter(adapter); - viewBinding.rootFolderCheckbox.setChecked(preferences.getTags().contains(FeedPreferences.TAG_ROOT)); + viewBinding.rootFolderCheckbox.setChecked(commonTags.contains(FeedPreferences.TAG_ROOT)); viewBinding.newTagButton.setOnClickListener(v -> addTag(viewBinding.newTagEditText.getText().toString().trim())); @@ -73,17 +81,16 @@ public class TagSettingsDialog extends DialogFragment { } }); + if (feedPreferencesList.size() > 1) { + viewBinding.commonTagsInfo.setVisibility(View.VISIBLE); + } + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); dialog.setView(viewBinding.getRoot()); - dialog.setTitle(R.string.feed_folders_label); + dialog.setTitle(R.string.feed_tags_label); dialog.setPositiveButton(android.R.string.ok, (d, input) -> { addTag(viewBinding.newTagEditText.getText().toString().trim()); - preferences.getTags().clear(); - preferences.getTags().addAll(displayedTags); - if (viewBinding.rootFolderCheckbox.isChecked()) { - preferences.getTags().add(FeedPreferences.TAG_ROOT); - } - DBWriter.setFeedPreferences(preferences); + updatePreferencesTags(feedPreferencesList, commonTags); }); dialog.setNegativeButton(R.string.cancel_label, null); return dialog.create(); @@ -96,7 +103,7 @@ public class TagSettingsDialog extends DialogFragment { List<NavDrawerData.DrawerItem> items = data.items; List<String> folders = new ArrayList<String>(); for (NavDrawerData.DrawerItem item : items) { - if (item.type == NavDrawerData.DrawerItem.Type.FOLDER) { + if (item.type == NavDrawerData.DrawerItem.Type.TAG) { folders.add(item.getTitle()); } } @@ -123,6 +130,17 @@ public class TagSettingsDialog extends DialogFragment { adapter.notifyDataSetChanged(); } + private void updatePreferencesTags(List<FeedPreferences> feedPreferencesList, Set<String> commonTags) { + if (viewBinding.rootFolderCheckbox.isChecked()) { + displayedTags.add(FeedPreferences.TAG_ROOT); + } + for (FeedPreferences preferences : feedPreferencesList) { + preferences.getTags().removeAll(commonTags); + preferences.getTags().addAll(displayedTags); + DBWriter.setFeedPreferences(preferences); + } + } + public class TagSelectionAdapter extends RecyclerView.Adapter<TagSelectionAdapter.ViewHolder> { @Override diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java index def2e56a7..2bce73b79 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/VariableSpeedDialog.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.dialog; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -15,10 +14,14 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.chip.Chip; import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.view.ItemOffsetDecoration; import de.danoeh.antennapod.view.PlaybackSpeedSeekBar; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -47,22 +50,12 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { super.onStart(); controller = new PlaybackController(getActivity()) { @Override - public void onPlaybackSpeedChange() { - updateSpeed(); - } - - @Override public void loadMediaInfo() { - updateSpeed(); + updateSpeed(new SpeedChangedEvent(controller.getCurrentPlaybackSpeedMultiplier())); } }; controller.init(); - speedSeekBar.setController(controller); - } - - private void updateSpeed() { - speedSeekBar.updateSpeed(); - addCurrentSpeedChip.setText(speedFormat.format(controller.getCurrentPlaybackSpeedMultiplier())); + EventBus.getDefault().register(this); } @Override @@ -70,6 +63,13 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { super.onStop(); controller.release(); controller = null; + EventBus.getDefault().unregister(this); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void updateSpeed(SpeedChangedEvent event) { + speedSeekBar.updateSpeed(event.getNewSpeed()); + addCurrentSpeedChip.setText(speedFormat.format(event.getNewSpeed())); } @Nullable @@ -78,6 +78,11 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { @Nullable Bundle savedInstanceState) { View root = View.inflate(getContext(), R.layout.speed_select_dialog, null); speedSeekBar = root.findViewById(R.id.speed_seek_bar); + speedSeekBar.setProgressChangedListener(multiplier -> { + if (controller != null) { + controller.setPlaybackSpeed(multiplier); + } + }); RecyclerView selectedSpeedsGrid = root.findViewById(R.id.selected_speeds_grid); selectedSpeedsGrid.setLayoutManager(new GridLayoutManager(getContext(), 3)); selectedSpeedsGrid.addItemDecoration(new ItemOffsetDecoration(getContext(), 4)); @@ -112,9 +117,7 @@ public class VariableSpeedDialog extends BottomSheetDialogFragment { @NonNull public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { Chip chip = new Chip(getContext()); - if (Build.VERSION.SDK_INT >= 17) { - chip.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); - } + chip.setTextAlignment(View.TEXT_ALIGNMENT_CENTER); return new ViewHolder(chip); } diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java index f97c1c7ab..340783208 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/GpodnetPodcastSearcher.java @@ -1,6 +1,6 @@ package de.danoeh.antennapod.discovery; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException; @@ -18,8 +18,8 @@ public class GpodnetPodcastSearcher implements PodcastSearcher { return Single.create((SingleOnSubscribe<List<PodcastSearchResult>>) subscriber -> { try { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(), - GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); + SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(), + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword()); List<GpodnetPodcast> gpodnetPodcasts = service.searchPodcasts(query, 0); List<PodcastSearchResult> results = new ArrayList<>(); for (GpodnetPodcast podcast : gpodnetPodcasts) { diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java index 6e894176f..5f3dd5f61 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java @@ -17,9 +17,12 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ItunesPodcastSearcher implements PodcastSearcher { private static final String ITUNES_API_URL = "https://itunes.apple.com/search?media=podcast&term=%s"; + private static final String PATTERN_BY_ID = ".*/podcasts\\.apple\\.com/.*/podcast/.*/id(\\d+).*"; public ItunesPodcastSearcher() { } @@ -70,9 +73,12 @@ public class ItunesPodcastSearcher implements PodcastSearcher { @Override public Single<String> lookupUrl(String url) { + Pattern pattern = Pattern.compile(PATTERN_BY_ID); + Matcher matcher = pattern.matcher(url); + final String lookupUrl = matcher.find() ? ("https://itunes.apple.com/lookup?id=" + matcher.group(1)) : url; return Single.create(emitter -> { OkHttpClient client = AntennapodHttpClient.getHttpClient(); - Request.Builder httpReq = new Request.Builder().url(url); + Request.Builder httpReq = new Request.Builder().url(lookupUrl); try { Response response = client.newCall(httpReq.build()).execute(); if (response.isSuccessful()) { @@ -92,7 +98,7 @@ public class ItunesPodcastSearcher implements PodcastSearcher { @Override public boolean urlNeedsLookup(String url) { - return url.contains("itunes.apple.com"); + return url.contains("itunes.apple.com") || url.matches(PATTERN_BY_ID); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java index 64e7f161e..8c01a4563 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.fragment; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ClipboardManager; import android.content.Context; @@ -12,9 +11,14 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.activity.result.contract.ActivityResultContracts.GetContent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.documentfile.provider.DocumentFile; @@ -48,14 +52,17 @@ import java.util.Collections; public class AddFeedFragment extends Fragment { public static final String TAG = "AddFeedFragment"; - private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1; - private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2; private static final String KEY_UP_ARROW = "up_arrow"; private AddfeedBinding viewBinding; private MainActivity activity; private boolean displayUpArrow; + private final ActivityResultLauncher<String> chooseOpmlImportPathLauncher = + registerForActivityResult(new GetContent(), this::chooseOpmlImportPathResult); + private final ActivityResultLauncher<Uri> addLocalFolderLauncher = + registerForActivityResult(new AddLocalFolder(), this::addLocalFolderResult); + @Override @Nullable public View onCreateView(@NonNull LayoutInflater inflater, @@ -91,10 +98,7 @@ public class AddFeedFragment extends Fragment { viewBinding.opmlImportButton.setOnClickListener(v -> { try { - Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); - intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); - intentGetContentAction.setType("*/*"); - startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); + chooseOpmlImportPathLauncher.launch("*/*"); } catch (ActivityNotFoundException e) { e.printStackTrace(); ((MainActivity) getActivity()) @@ -107,9 +111,7 @@ public class AddFeedFragment extends Fragment { return; } try { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivityForResult(intent, REQUEST_CODE_ADD_LOCAL_FOLDER); + addLocalFolderLauncher.launch(null); } catch (ActivityNotFoundException e) { e.printStackTrace(); ((MainActivity) getActivity()) @@ -157,6 +159,10 @@ public class AddFeedFragment extends Fragment { } private void performSearch() { + viewBinding.combinedFeedSearchEditText.clearFocus(); + InputMethodManager in = (InputMethodManager) + getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + in.hideSoftInputFromWindow(viewBinding.combinedFeedSearchEditText.getWindowToken(), 0); String query = viewBinding.combinedFeedSearchEditText.getText().toString(); if (query.matches("http[s]?://.*")) { addUrl(query); @@ -171,22 +177,23 @@ public class AddFeedFragment extends Fragment { setRetainInstance(true); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK || data == null) { + private void chooseOpmlImportPathResult(final Uri uri) { + if (uri == null) { return; } - Uri uri = data.getData(); - - if (requestCode == REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH) { - Intent intent = new Intent(getContext(), OpmlImportActivity.class); - intent.setData(uri); - startActivity(intent); - } else if (requestCode == REQUEST_CODE_ADD_LOCAL_FOLDER) { - Observable.fromCallable(() -> addLocalFolder(uri)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( + final Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + + private void addLocalFolderResult(final Uri uri) { + if (uri == null) { + return; + } + Observable.fromCallable(() -> addLocalFolder(uri)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( feed -> { Fragment fragment = FeedItemlistFragment.newInstance(feed.getId()); ((MainActivity) getActivity()).loadChildFragment(fragment); @@ -195,7 +202,6 @@ public class AddFeedFragment extends Fragment { ((MainActivity) getActivity()) .showSnackbarAbovePlayer(error.getLocalizedMessage(), Snackbar.LENGTH_LONG); }); - } } private Feed addLocalFolder(Uri uri) throws DownloadRequestException { @@ -219,4 +225,14 @@ public class AddFeedFragment extends Fragment { DBTasks.forceRefreshFeed(getContext(), fromDatabase, true); return fromDatabase; } + + private static class AddLocalFolder extends ActivityResultContracts.OpenDocumentTree { + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @NonNull + @Override + public Intent createIntent(@NonNull final Context context, @Nullable final Uri input) { + return super.createIntent(context, input) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java index 168133c7a..95e2eb1aa 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java @@ -25,6 +25,14 @@ import androidx.viewpager2.widget.ViewPager2; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.snackbar.Snackbar; +import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.event.playback.BufferUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; +import de.danoeh.antennapod.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; +import de.danoeh.antennapod.playback.cast.CastEnabledActivity; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -34,25 +42,20 @@ import java.text.NumberFormat; import java.util.List; 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.event.ServiceEvent; +import de.danoeh.antennapod.event.FavoritesEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.model.feed.Chapter; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.ChapterUtils; 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.model.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; @@ -224,8 +227,8 @@ public class AudioPlayerFragment extends Fragment implements } @Subscribe(threadMode = ThreadMode.MAIN) - public void onPlaybackServiceChanged(ServiceEvent event) { - if (event.action == ServiceEvent.Action.SERVICE_SHUT_DOWN) { + public void onPlaybackServiceChanged(PlaybackServiceEvent event) { + if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) { ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED); } } @@ -243,14 +246,11 @@ public class AudioPlayerFragment extends Fragment implements }); } - protected void updatePlaybackSpeedButton(Playable media) { - if (butPlaybackSpeed == null || controller == null) { - return; - } - float speed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(media); - String speedStr = new DecimalFormat("0.00").format(speed); + @Subscribe(threadMode = ThreadMode.MAIN) + public void updatePlaybackSpeedButton(SpeedChangedEvent event) { + String speedStr = new DecimalFormat("0.00").format(event.getNewSpeed()); txtvPlaybackSpeed.setText(speedStr); - butPlaybackSpeed.setSpeed(speed); + butPlaybackSpeed.setSpeed(event.getNewSpeed()); } private void loadMediaInfo(boolean includingChapters) { @@ -282,47 +282,6 @@ public class AudioPlayerFragment extends Fragment implements private PlaybackController newPlaybackController() { return new PlaybackController(getActivity()) { @Override - public void onBufferStart() { - progressIndicator.setVisibility(View.VISIBLE); - } - - @Override - public void onBufferEnd() { - progressIndicator.setVisibility(View.GONE); - } - - @Override - public void onBufferUpdate(float progress) { - if (isStreaming()) { - sbPosition.setSecondaryProgress((int) (progress * sbPosition.getMax())); - } else { - sbPosition.setSecondaryProgress(0); - } - } - - @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.setPositiveButton(android.R.string.ok, (dialog, which) -> - ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED)); - if (!UserPreferences.useExoplayer()) { - errorDialog.setNeutralButton(R.string.media_player_switch_to_exoplayer, (dialog, which) -> { - UserPreferences.enableExoplayer(); - ((MainActivity) getActivity()).showSnackbarAbovePlayer( - R.string.media_player_switched_to_exoplayer, Snackbar.LENGTH_LONG); - }); - } - errorDialog.create().show(); - } - - @Override - public void onSleepTimerUpdate() { - AudioPlayerFragment.this.loadMediaInfo(false); - } - - @Override protected void updatePlayButtonShowsPlay(boolean showPlay) { butPlay.setIsShowPlay(showPlay); } @@ -336,25 +295,28 @@ public class AudioPlayerFragment extends Fragment implements public void onPlaybackEnd() { ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED); } - - @Override - public void onPlaybackSpeedChange() { - updatePlaybackSpeedButton(getMedia()); - } }; } private void updateUi(Playable media) { - if (controller == null) { + if (controller == null || media == null) { return; } duration = controller.getDuration(); - updatePosition(new PlaybackPositionEvent(controller.getPosition(), duration)); - updatePlaybackSpeedButton(media); + updatePosition(new PlaybackPositionEvent(media.getPosition(), media.getDuration())); + updatePlaybackSpeedButton(new SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media))); setChapterDividers(media); setupOptionsMenu(media); } + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void sleepTimerUpdate(SleepTimerUpdatedEvent event) { + if (event.isCancelled() || event.wasJustEnabled()) { + AudioPlayerFragment.this.loadMediaInfo(false); + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -385,6 +347,20 @@ public class AudioPlayerFragment extends Fragment implements } @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void bufferUpdate(BufferUpdateEvent event) { + if (event.hasStarted()) { + progressIndicator.setVisibility(View.VISIBLE); + } else if (event.hasEnded()) { + progressIndicator.setVisibility(View.GONE); + } else if (controller != null && controller.isStreaming()) { + sbPosition.setSecondaryProgress((int) (event.getProgress() * sbPosition.getMax())); + } else { + sbPosition.setSecondaryProgress(0); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) public void updatePosition(PlaybackPositionEvent event) { if (controller == null || txtvPosition == null || txtvLength == null || sbPosition == null) { return; @@ -419,6 +395,23 @@ public class AudioPlayerFragment extends Fragment implements AudioPlayerFragment.this.loadMediaInfo(false); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void mediaPlayerError(PlayerErrorEvent event) { + final AlertDialog.Builder errorDialog = new AlertDialog.Builder(getContext()); + errorDialog.setTitle(R.string.error_label); + errorDialog.setMessage(event.getMessage()); + errorDialog.setPositiveButton(android.R.string.ok, (dialog, which) -> + ((MainActivity) getActivity()).getBottomSheet().setState(BottomSheetBehavior.STATE_COLLAPSED)); + if (!UserPreferences.useExoplayer()) { + errorDialog.setNeutralButton(R.string.media_player_switch_to_exoplayer, (dialog, which) -> { + UserPreferences.enableExoplayer(); + ((MainActivity) getActivity()).showSnackbarAbovePlayer( + R.string.media_player_switched_to_exoplayer, Snackbar.LENGTH_LONG); + }); + } + errorDialog.create().show(); + } + @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (controller == null || txtvLength == null) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java index de14f220e..04ad6e2bd 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -17,14 +17,14 @@ import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import de.danoeh.antennapod.playback.base.PlayerStatus; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.ChaptersListAdapter; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.util.playback.PlaybackController; import de.danoeh.antennapod.model.feed.Chapter; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java index 6c8baef29..933147378 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java @@ -23,10 +23,10 @@ import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.adapter.actionbutton.DeleteActionButton; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloadLogEvent; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler; import de.danoeh.antennapod.model.feed.FeedItem; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index 8c2203f72..2d448faa8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -45,7 +45,7 @@ import org.greenrobot.eventbus.ThreadMode; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; @@ -110,9 +110,8 @@ public class CoverFragment extends Fragment { butNextChapter.setColorFilter(colorFilter); butPrevChapter.setColorFilter(colorFilter); descriptionIcon.setColorFilter(colorFilter); - ChaptersFragment chaptersFragment = new ChaptersFragment(); chapterControl.setOnClickListener(v -> - chaptersFragment.show(getChildFragmentManager(), ChaptersFragment.TAG)); + new ChaptersFragment().show(getChildFragmentManager(), ChaptersFragment.TAG)); butPrevChapter.setOnClickListener(v -> seekToPrevChapter()); butNextChapter.setOnClickListener(v -> seekToNextChapter()); @@ -156,8 +155,13 @@ public class CoverFragment extends Fragment { + "・" + "\u00A0" + StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0")); - Intent openFeed = MainActivity.getIntentToOpenFeed(requireContext(), ((FeedMedia) media).getItem().getFeedId()); - txtvPodcastTitle.setOnClickListener(v -> startActivity(openFeed)); + if (media instanceof FeedMedia) { + Intent openFeed = MainActivity.getIntentToOpenFeed(requireContext(), + ((FeedMedia) media).getItem().getFeedId()); + txtvPodcastTitle.setOnClickListener(v -> startActivity(openFeed)); + } else { + txtvPodcastTitle.setOnClickListener(null); + } txtvPodcastTitle.setOnLongClickListener(v -> copyText(media.getFeedTitle())); txtvEpisodeTitle.setText(media.getEpisodeTitle()); txtvEpisodeTitle.setOnLongClickListener(v -> copyText(media.getEpisodeTitle())); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DiscoveryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DiscoveryFragment.java index 034b111e1..230a0ce0d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DiscoveryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DiscoveryFragment.java @@ -23,7 +23,7 @@ import org.greenrobot.eventbus.EventBus; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.itunes.ItunesAdapter; -import de.danoeh.antennapod.core.event.DiscoveryDefaultUpdateEvent; +import de.danoeh.antennapod.event.DiscoveryDefaultUpdateEvent; import de.danoeh.antennapod.discovery.ItunesTopListLoader; import de.danoeh.antennapod.discovery.PodcastSearchResult; import io.reactivex.disposables.Disposable; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index ddbf6c078..5602dcb78 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java @@ -113,7 +113,7 @@ public class DownloadLogFragment extends ListFragment { if (downloadRequest.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { FeedMedia media = DBReader.getFeedMedia(downloadRequest.getFeedfileId()); FeedItem feedItem = media.getItem(); - feedItem.setAutoDownload(false); + feedItem.disableAutoDownload(); DBWriter.setFeedItem(feedItem); } } else if (item instanceof DownloadStatus) { diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java index 7ea76bb8d..37d77d31f 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java @@ -21,10 +21,10 @@ import android.widget.TextView; import android.widget.Toast; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.view.EpisodeItemListRecyclerView; import de.danoeh.antennapod.view.viewholder.EpisodeItemViewHolder; @@ -40,7 +40,7 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.storage.DBWriter; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index 8e070738c..1e24d62f7 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -9,21 +9,23 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; + +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.bottomsheet.BottomSheetBehavior; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.ServiceEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.model.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; +import de.danoeh.antennapod.playback.base.PlayerStatus; import de.danoeh.antennapod.view.PlayButton; import io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -77,8 +79,8 @@ public class ExternalPlayerFragment extends Fragment { } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); butPlay.setOnClickListener(v -> { if (controller == null) { return; @@ -97,12 +99,6 @@ public class ExternalPlayerFragment extends Fragment { private PlaybackController setupPlaybackController() { return new PlaybackController(getActivity()) { - - @Override - public void onPositionObserverUpdate() { - ExternalPlayerFragment.this.onPositionObserverUpdate(); - } - @Override protected void updatePlayButtonShowsPlay(boolean showPlay) { butPlay.setIsShowPlay(showPlay); @@ -140,13 +136,20 @@ public class ExternalPlayerFragment extends Fragment { } @Subscribe(threadMode = ThreadMode.MAIN) - public void onEventMainThread(PlaybackPositionEvent event) { - onPositionObserverUpdate(); + public void onPositionObserverUpdate(PlaybackPositionEvent event) { + if (controller == null) { + return; + } else if (controller.getPosition() == PlaybackService.INVALID_TIME + || controller.getDuration() == PlaybackService.INVALID_TIME) { + return; + } + progressBar.setProgress((int) + ((double) controller.getPosition() / controller.getDuration() * 100)); } @Subscribe(threadMode = ThreadMode.MAIN) - public void onPlaybackServiceChanged(ServiceEvent event) { - if (event.action == ServiceEvent.Action.SERVICE_SHUT_DOWN) { + public void onPlaybackServiceChanged(PlaybackServiceEvent event) { + if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) { ((MainActivity) getActivity()).setPlayerVisible(false); } } @@ -193,7 +196,7 @@ public class ExternalPlayerFragment extends Fragment { ((MainActivity) getActivity()).setPlayerVisible(true); txtvTitle.setText(media.getEpisodeTitle()); feedName.setText(media.getFeedTitle()); - onPositionObserverUpdate(); + onPositionObserverUpdate(new PlaybackPositionEvent(media.getPosition(), media.getDuration())); RequestOptions options = new RequestOptions() .placeholder(R.color.light_gray) @@ -218,15 +221,4 @@ public class ExternalPlayerFragment extends Fragment { ((MainActivity) getActivity()).getBottomSheet().setLocked(false); } } - - private void onPositionObserverUpdate() { - if (controller == null) { - return; - } else if (controller.getPosition() == PlaybackService.INVALID_TIME - || controller.getDuration() == PlaybackService.INVALID_TIME) { - return; - } - progressBar.setProgress((int) - ((double) controller.getPosition() / controller.getDuration() * 100)); - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java index 986c417fd..d7bfd404d 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FavoriteEpisodesFragment.java @@ -18,7 +18,7 @@ import org.greenrobot.eventbus.Subscribe; import java.util.List; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.FavoritesEvent; +import de.danoeh.antennapod.event.FavoritesEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java index da7e7e633..ae298cc1c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -1,6 +1,5 @@ package de.danoeh.antennapod.fragment; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.Context; @@ -10,62 +9,56 @@ import android.graphics.LightingColorFilter; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.AppCompatDrawableManager; -import androidx.appcompat.widget.Toolbar; -import androidx.documentfile.provider.DocumentFile; -import androidx.fragment.app.Fragment; import android.text.TextUtils; -import android.text.format.Formatter; import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.AppCompatDrawableManager; +import androidx.appcompat.widget.Toolbar; +import androidx.documentfile.provider.DocumentFile; +import androidx.fragment.app.Fragment; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.snackbar.Snackbar; import com.joanzapata.iconify.Iconify; - -import org.apache.commons.lang3.StringUtils; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; -import de.danoeh.antennapod.model.feed.Feed; -import de.danoeh.antennapod.model.feed.FeedFunding; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.FastBlurTransformation; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DownloadRequestException; -import de.danoeh.antennapod.core.storage.StatisticsItem; -import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText; import de.danoeh.antennapod.fragment.preferences.StatisticsFragment; import de.danoeh.antennapod.menuhandler.FeedMenuHandler; +import de.danoeh.antennapod.model.feed.Feed; +import de.danoeh.antennapod.model.feed.FeedFunding; import de.danoeh.antennapod.view.ToolbarIconTintManager; import io.reactivex.Completable; import io.reactivex.Maybe; import io.reactivex.MaybeOnSubscribe; -import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.HashSet; -import java.util.List; -import java.util.Locale; /** * Displays information about a feed. @@ -74,28 +67,22 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; private static final String TAG = "FeedInfoActivity"; - private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2; + private final ActivityResultLauncher<Uri> addLocalFolderLauncher = + registerForActivityResult(new AddLocalFolder(), this::addLocalFolderResult); private Feed feed; private Disposable disposable; - private Disposable disposableStatistics; private ImageView imgvCover; private TextView txtvTitle; private TextView txtvDescription; - private TextView lblStatistics; - private TextView txtvPodcastTime; - private TextView txtvPodcastSpace; - private TextView txtvPodcastEpisodeCount; private TextView txtvFundingUrl; private TextView lblSupport; - private Button btnvOpenStatistics; private TextView txtvUrl; private TextView txtvAuthorHeader; private ImageView imgvBackground; private View infoContainer; private View header; private Toolbar toolbar; - private ToolbarIconTintManager iconTintManager; public static FeedInfoFragment newInstance(Feed feed) { FeedInfoFragment fragment = new FeedInfoFragment(); @@ -133,7 +120,7 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic AppBarLayout appBar = root.findViewById(R.id.appBar); CollapsingToolbarLayout collapsingToolbar = root.findViewById(R.id.collapsing_toolbar); - iconTintManager = new ToolbarIconTintManager(getContext(), toolbar, collapsingToolbar) { + ToolbarIconTintManager iconTintManager = new ToolbarIconTintManager(getContext(), toolbar, collapsingToolbar) { @Override protected void doTint(Context themedContext) { toolbar.getMenu().findItem(R.id.visit_website_item) @@ -157,23 +144,20 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic imgvBackground.setColorFilter(new LightingColorFilter(0xff828282, 0x000000)); txtvDescription = root.findViewById(R.id.txtvDescription); - lblStatistics = root.findViewById(R.id.lblStatistics); - txtvPodcastSpace = root.findViewById(R.id.txtvPodcastSpaceUsed); - txtvPodcastEpisodeCount = root.findViewById(R.id.txtvPodcastEpisodeCount); - txtvPodcastTime = root.findViewById(R.id.txtvPodcastTime); - btnvOpenStatistics = root.findViewById(R.id.btnvOpenStatistics); txtvUrl = root.findViewById(R.id.txtvUrl); lblSupport = root.findViewById(R.id.lblSupport); txtvFundingUrl = root.findViewById(R.id.txtvFundingUrl); txtvUrl.setOnClickListener(copyUrlToClipboard); - btnvOpenStatistics.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - StatisticsFragment fragment = new StatisticsFragment(); - ((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE); - } + long feedId = getArguments().getLong(EXTRA_FEED_ID); + getParentFragmentManager().beginTransaction().replace(R.id.statisticsFragmentContainer, + FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment") + .commitAllowingStateLoss(); + + root.findViewById(R.id.btnvOpenStatistics).setOnClickListener(view -> { + StatisticsFragment fragment = new StatisticsFragment(); + ((MainActivity) getActivity()).loadChildFragment(fragment, TransitionEffect.SLIDE); }); return root; @@ -195,7 +179,6 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic .subscribe(result -> { feed = result; showFeed(); - loadStatistics(); }, error -> Log.d(TAG, Log.getStackTraceString(error)), () -> { }); } @@ -270,53 +253,12 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic refreshToolbarState(); } - private void loadStatistics() { - if (disposableStatistics != null) { - disposableStatistics.dispose(); - } - - disposableStatistics = - Observable.fromCallable(() -> { - List<StatisticsItem> statisticsData = DBReader.getStatistics(); - - for (StatisticsItem statisticsItem : statisticsData) { - if (statisticsItem.feed.getId() == feed.getId()) { - return statisticsItem; - } - } - - return null; - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - txtvPodcastTime.setText(Converter.shortLocalizedDuration( - getContext(), result.timePlayed)); - txtvPodcastSpace.setText(Formatter.formatShortFileSize( - getContext(), result.totalDownloadSize)); - txtvPodcastEpisodeCount.setText(String.format(Locale.getDefault(), - "%d%s", result.episodesDownloadCount, - getString(R.string.episodes_suffix))); - }, error -> { - Log.d(TAG, Log.getStackTraceString(error)); - lblStatistics.setVisibility(View.GONE); - txtvPodcastSpace.setVisibility(View.GONE); - txtvPodcastTime.setVisibility(View.GONE); - txtvPodcastEpisodeCount.setVisibility(View.GONE); - btnvOpenStatistics.setVisibility(View.GONE); - }); - } - @Override public void onDestroy() { super.onDestroy(); if (disposable != null) { disposable.dispose(); } - - if (disposableStatistics != null) { - disposableStatistics.dispose(); - } } private void refreshToolbarState() { @@ -351,9 +293,7 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic alert.setMessage(R.string.reconnect_local_folder_warning); alert.setPositiveButton(android.R.string.ok, (dialog, which) -> { try { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivityForResult(intent, REQUEST_CODE_ADD_LOCAL_FOLDER); + addLocalFolderLauncher.launch(null); } catch (ActivityNotFoundException e) { Log.e(TAG, "No activity found. Should never happen..."); } @@ -366,16 +306,11 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic return handled; } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK || data == null) { + private void addLocalFolderResult(final Uri uri) { + if (uri == null) { return; } - Uri uri = data.getData(); - - if (requestCode == REQUEST_CODE_ADD_LOCAL_FOLDER) { - reconnectLocalFolder(uri); - } + reconnectLocalFolder(uri); } private void reconnectLocalFolder(Uri uri) { @@ -401,4 +336,14 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic error -> ((MainActivity) getActivity()) .showSnackbarAbovePlayer(error.getLocalizedMessage(), Snackbar.LENGTH_LONG)); } + + private static class AddLocalFolder extends ActivityResultContracts.OpenDocumentTree { + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @NonNull + @Override + public Intent createIntent(@NonNull final Context context, @Nullable final Uri input) { + return super.createIntent(context, input) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java index 0ee60866d..148cf6582 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -52,13 +52,13 @@ import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FavoritesEvent; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.QueueEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FavoritesEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.QueueEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedEvent; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.FastBlurTransformation; @@ -333,8 +333,8 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem new RenameFeedDialog(getActivity(), feed).show(); return true; } else if (itemId == R.id.remove_item) { - RemoveFeedDialog.show(getContext(), feed, () -> - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null)); + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); + RemoveFeedDialog.show(getContext(), feed); return true; } else if (itemId == R.id.action_search) { ((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance(feed.getId(), feed.getTitle())); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java index dbc7f2ae3..0c2103d25 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java @@ -7,16 +7,19 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.preference.ListPreference; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.SwitchPreferenceCompat; import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.event.settings.SkipIntroEndingChangedEvent; -import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent; -import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent; +import de.danoeh.antennapod.event.settings.SkipIntroEndingChangedEvent; +import de.danoeh.antennapod.event.settings.SpeedPresetChangedEvent; +import de.danoeh.antennapod.event.settings.VolumeAdaptionChangedEvent; +import de.danoeh.antennapod.databinding.PlaybackSpeedFeedSettingDialogBinding; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedFilter; import de.danoeh.antennapod.model.feed.FeedPreferences; @@ -35,12 +38,9 @@ import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import org.greenrobot.eventbus.EventBus; -import java.text.DecimalFormat; -import java.text.DecimalFormatSymbols; +import java.util.Collections; import java.util.Locale; -import static de.danoeh.antennapod.model.feed.FeedPreferences.SPEED_USE_GLOBAL; - public class FeedSettingsFragment extends Fragment { private static final String TAG = "FeedSettingsFragment"; private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; @@ -104,8 +104,6 @@ public class FeedSettingsFragment extends Fragment { private static final String PREF_FEED_PLAYBACK_SPEED = "feedPlaybackSpeed"; private static final String PREF_AUTO_SKIP = "feedAutoSkip"; private static final String PREF_TAGS = "tags"; - private static final DecimalFormat SPEED_FORMAT = - new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); private Feed feed; private Disposable disposable; @@ -164,7 +162,6 @@ public class FeedSettingsFragment extends Fragment { updateAutoDeleteSummary(); updateVolumeReductionValue(); updateAutoDownloadEnabled(); - updatePlaybackSpeedPreference(); if (feed.isLocalFeed()) { findPreference(PREF_AUTHENTICATION).setVisible(false); @@ -205,27 +202,34 @@ public class FeedSettingsFragment extends Fragment { } private void setupPlaybackSpeedPreference() { - ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); - - final String[] speeds = getResources().getStringArray(R.array.playback_speed_values); - String[] values = new String[speeds.length + 1]; - values[0] = SPEED_FORMAT.format(SPEED_USE_GLOBAL); - - String[] entries = new String[speeds.length + 1]; - entries[0] = getString(R.string.feed_auto_download_global); - - System.arraycopy(speeds, 0, values, 1, speeds.length); - System.arraycopy(speeds, 0, entries, 1, speeds.length); - - feedPlaybackSpeedPreference.setEntryValues(values); - feedPlaybackSpeedPreference.setEntries(entries); - feedPlaybackSpeedPreference.setOnPreferenceChangeListener((preference, newValue) -> { - feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) newValue)); - DBWriter.setFeedPreferences(feedPreferences); - updatePlaybackSpeedPreference(); - EventBus.getDefault().post( - new SpeedPresetChangedEvent(feedPreferences.getFeedPlaybackSpeed(), feed.getId())); - return false; + Preference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); + feedPlaybackSpeedPreference.setOnPreferenceClickListener(preference -> { + PlaybackSpeedFeedSettingDialogBinding viewBinding = + PlaybackSpeedFeedSettingDialogBinding.inflate(getLayoutInflater()); + viewBinding.seekBar.setProgressChangedListener(speed -> + viewBinding.currentSpeedLabel.setText(String.format(Locale.getDefault(), "%.2fx", speed))); + float speed = feedPreferences.getFeedPlaybackSpeed(); + viewBinding.useGlobalCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> { + viewBinding.seekBar.setEnabled(!isChecked); + viewBinding.seekBar.setAlpha(isChecked ? 0.4f : 1f); + viewBinding.currentSpeedLabel.setAlpha(isChecked ? 0.4f : 1f); + }); + viewBinding.useGlobalCheckbox.setChecked(speed == FeedPreferences.SPEED_USE_GLOBAL); + viewBinding.seekBar.updateSpeed(speed == FeedPreferences.SPEED_USE_GLOBAL ? 1 : speed); + new AlertDialog.Builder(getContext()) + .setTitle(R.string.playback_speed) + .setView(viewBinding.getRoot()) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + float newSpeed = viewBinding.useGlobalCheckbox.isChecked() + ? FeedPreferences.SPEED_USE_GLOBAL : viewBinding.seekBar.getCurrentSpeed(); + feedPreferences.setFeedPlaybackSpeed(newSpeed); + DBWriter.setFeedPreferences(feedPreferences); + EventBus.getDefault().post( + new SpeedPresetChangedEvent(feedPreferences.getFeedPlaybackSpeed(), feed.getId())); + }) + .setNegativeButton(R.string.cancel_label, null) + .show(); + return true; }); } @@ -277,13 +281,6 @@ public class FeedSettingsFragment extends Fragment { }); } - private void updatePlaybackSpeedPreference() { - ListPreference feedPlaybackSpeedPreference = findPreference(PREF_FEED_PLAYBACK_SPEED); - - float speedValue = feedPreferences.getFeedPlaybackSpeed(); - feedPlaybackSpeedPreference.setValue(SPEED_FORMAT.format(speedValue)); - } - private void updateAutoDeleteSummary() { ListPreference autoDeletePreference = findPreference(PREF_AUTO_DELETE); @@ -395,7 +392,8 @@ public class FeedSettingsFragment extends Fragment { private void setupTags() { findPreference(PREF_TAGS).setOnPreferenceClickListener(preference -> { - TagSettingsDialog.newInstance(feedPreferences).show(getChildFragmentManager(), TagSettingsDialog.TAG); + TagSettingsDialog.newInstance(Collections.singletonList(feedPreferences)) + .show(getChildFragmentManager(), TagSettingsDialog.TAG); return true; }); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsDialogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsDialogFragment.java new file mode 100644 index 000000000..33710b2c4 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsDialogFragment.java @@ -0,0 +1,42 @@ +package de.danoeh.antennapod.fragment; + +import android.app.Dialog; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import de.danoeh.antennapod.R; + +public class FeedStatisticsDialogFragment extends DialogFragment { + private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; + private static final String EXTRA_FEED_TITLE = "de.danoeh.antennapod.extra.feedTitle"; + + public static FeedStatisticsDialogFragment newInstance(long feedId, String feedTitle) { + FeedStatisticsDialogFragment fragment = new FeedStatisticsDialogFragment(); + Bundle arguments = new Bundle(); + arguments.putLong(EXTRA_FEED_ID, feedId); + arguments.putString(EXTRA_FEED_TITLE, feedTitle); + fragment.setArguments(arguments); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + dialog.setPositiveButton(android.R.string.ok, null); + dialog.setTitle(getArguments().getString(EXTRA_FEED_TITLE)); + dialog.setView(R.layout.feed_statistics_dialog); + return dialog.create(); + } + + @Override + public void onStart() { + super.onStart(); + long feedId = getArguments().getLong(EXTRA_FEED_ID); + getChildFragmentManager().beginTransaction().replace(R.id.statisticsContainer, + FeedStatisticsFragment.newInstance(feedId, true), "feed_statistics_fragment") + .commitAllowingStateLoss(); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java new file mode 100644 index 000000000..e85c2a386 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java @@ -0,0 +1,93 @@ +package de.danoeh.antennapod.fragment; + +import android.os.Bundle; +import android.text.format.Formatter; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import de.danoeh.antennapod.core.storage.DBReader; +import de.danoeh.antennapod.core.storage.StatisticsItem; +import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.databinding.FeedStatisticsBinding; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.disposables.Disposable; +import io.reactivex.schedulers.Schedulers; + +import java.util.List; +import java.util.Locale; + +public class FeedStatisticsFragment extends Fragment { + private static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId"; + private static final String EXTRA_DETAILED = "de.danoeh.antennapod.extra.detailed"; + + private long feedId; + private Disposable disposable; + private FeedStatisticsBinding viewBinding; + + public static FeedStatisticsFragment newInstance(long feedId, boolean detailed) { + FeedStatisticsFragment fragment = new FeedStatisticsFragment(); + Bundle arguments = new Bundle(); + arguments.putLong(EXTRA_FEED_ID, feedId); + arguments.putBoolean(EXTRA_DETAILED, detailed); + fragment.setArguments(arguments); + return fragment; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + feedId = getArguments().getLong(EXTRA_FEED_ID); + viewBinding = FeedStatisticsBinding.inflate(inflater); + + if (!getArguments().getBoolean(EXTRA_DETAILED)) { + for (int i = 0; i < viewBinding.getRoot().getChildCount(); i++) { + View child = viewBinding.getRoot().getChildAt(i); + if ("detailed".equals(child.getTag())) { + child.setVisibility(View.GONE); + } + } + } + + loadStatistics(); + return viewBinding.getRoot(); + } + + private void loadStatistics() { + disposable = + Observable.fromCallable(() -> { + List<StatisticsItem> statisticsData = DBReader.getStatistics(); + for (StatisticsItem statisticsItem : statisticsData) { + if (statisticsItem.feed.getId() == feedId) { + return statisticsItem; + } + } + return null; + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::showStats, Throwable::printStackTrace); + } + + private void showStats(StatisticsItem s) { + viewBinding.startedTotalLabel.setText(String.format(Locale.getDefault(), "%d / %d", + s.episodesStarted, s.episodes)); + viewBinding.timePlayedLabel.setText(Converter.shortLocalizedDuration(getContext(), s.timePlayed)); + viewBinding.durationPlayedLabel.setText(Converter.shortLocalizedDuration(getContext(), s.timePlayedCountAll)); + viewBinding.totalDurationLabel.setText(Converter.shortLocalizedDuration(getContext(), s.time)); + viewBinding.onDeviceLabel.setText(String.format(Locale.getDefault(), "%d", s.episodesDownloadCount)); + viewBinding.spaceUsedLabel.setText(Formatter.formatShortFileSize(getContext(), s.totalDownloadSize)); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (disposable != null) { + disposable.dispose(); + } + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index 31c6da8cd..c261370e2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -14,7 +14,6 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.text.TextUtilsCompat; import androidx.core.util.ObjectsCompat; @@ -42,9 +41,9 @@ import de.danoeh.antennapod.adapter.actionbutton.StreamActionButton; import de.danoeh.antennapod.adapter.actionbutton.VisitWebsiteActionButton; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; @@ -224,17 +223,6 @@ public class ItemFragment extends Fragment { } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - load(); - } - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - } - - @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); @@ -245,6 +233,7 @@ public class ItemFragment extends Fragment { } }; controller.init(); + load(); } @Override @@ -398,7 +387,7 @@ public class ItemFragment extends Fragment { long mediaId = item.getMedia().getId(); if (ArrayUtils.contains(update.mediaIds, mediaId)) { if (itemsLoaded && getActivity() != null) { - updateAppearance(); + updateButtons(); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java index d42300ca7..14f6ae875 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemPagerFragment.java @@ -20,7 +20,7 @@ import org.greenrobot.eventbus.ThreadMode; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java index 826a7e0ab..18defc545 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java @@ -28,9 +28,9 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.NavListAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.QueueEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.QueueEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.dialog.TagSettingsDialog; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -50,6 +50,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -157,16 +158,16 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS }; removeAllNewFlagsConfirmationDialog.createNewDialog().show(); return true; - } else if (itemId == R.id.add_to_folder) { - TagSettingsDialog.newInstance(feed.getPreferences()).show(getChildFragmentManager(), TagSettingsDialog.TAG); + } else if (itemId == R.id.edit_tags) { + TagSettingsDialog.newInstance(Collections.singletonList(feed.getPreferences())) + .show(getChildFragmentManager(), TagSettingsDialog.TAG); return true; } else if (itemId == R.id.rename_item) { new RenameFeedDialog(getActivity(), feed).show(); return true; } else if (itemId == R.id.remove_item) { - RemoveFeedDialog.show(getContext(), feed, () -> { - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); - }); + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); + RemoveFeedDialog.show(getContext(), feed); return true; } return super.onContextItemSelected(item); @@ -318,7 +319,7 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS ((MainActivity) getActivity()).getBottomSheet() .setState(BottomSheetBehavior.STATE_COLLAPSED); } else { - NavDrawerData.FolderDrawerItem folder = ((NavDrawerData.FolderDrawerItem) clickedItem); + NavDrawerData.TagDrawerItem folder = ((NavDrawerData.TagDrawerItem) clickedItem); if (openFolders.contains(folder.name)) { openFolders.remove(folder.name); } else { @@ -388,11 +389,11 @@ public class NavDrawerFragment extends Fragment implements SharedPreferences.OnS for (NavDrawerData.DrawerItem item : items) { item.setLayer(layer); flatItems.add(item); - if (item.type == NavDrawerData.DrawerItem.Type.FOLDER) { - NavDrawerData.FolderDrawerItem folder = ((NavDrawerData.FolderDrawerItem) item); + if (item.type == NavDrawerData.DrawerItem.Type.TAG) { + NavDrawerData.TagDrawerItem folder = ((NavDrawerData.TagDrawerItem) item); folder.isOpen = openFolders.contains(folder.name); if (folder.isOpen) { - flatItems.addAll(makeFlatDrawerData(((NavDrawerData.FolderDrawerItem) item).children, layer + 1)); + flatItems.addAll(makeFlatDrawerData(((NavDrawerData.TagDrawerItem) item).children, layer + 1)); } } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java index 992b6930c..f3080f655 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java @@ -1,15 +1,20 @@ package de.danoeh.antennapod.fragment; +import android.content.Context; import android.content.Intent; import android.os.Bundle; + +import android.widget.AbsListView; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.appcompat.widget.SearchView; + import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.GridView; import android.widget.ProgressBar; @@ -110,6 +115,21 @@ public class OnlineSearchFragment extends Fragment { TextView txtvPoweredBy = root.findViewById(R.id.search_powered_by); txtvPoweredBy.setText(getString(R.string.search_powered_by, searchProvider.getName())); setupToolbar(root.findViewById(R.id.toolbar)); + + gridView.setOnScrollListener(new AbsListView.OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + if (scrollState == SCROLL_STATE_TOUCH_SCROLL) { + InputMethodManager imm = (InputMethodManager) + getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { + } + }); return root; } @@ -142,6 +162,11 @@ public class OnlineSearchFragment extends Fragment { return false; } }); + sv.setOnQueryTextFocusChangeListener((view, hasFocus) -> { + if (hasFocus) { + showInputMethod(view.findFocus()); + } + }); searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() { @Override public boolean onMenuItemActionExpand(MenuItem item) { @@ -192,4 +217,11 @@ public class OnlineSearchFragment extends Fragment { txtvEmpty.setVisibility(View.GONE); progressBar.setVisibility(View.VISIBLE); } + + private void showInputMethod(View view) { + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(view, 0); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index 5e3d36c03..e1fa5eeb6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -16,11 +16,11 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlaybackHistoryEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.playback.PlaybackHistoryEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index 1b7d236c6..b308db0f6 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -33,11 +33,11 @@ import de.danoeh.antennapod.adapter.QueueRecyclerAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.QueueEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.QueueEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.fragment.actions.EpisodeMultiSelectActionHandler; import de.danoeh.antennapod.fragment.swipeactions.SwipeActions; @@ -247,8 +247,9 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi () -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); private void refreshToolbarState() { - toolbar.getMenu().findItem(R.id.queue_lock).setChecked(UserPreferences.isQueueLocked()); boolean keepSorted = UserPreferences.isQueueKeepSorted(); + toolbar.getMenu().findItem(R.id.queue_lock).setChecked(UserPreferences.isQueueLocked()); + toolbar.getMenu().findItem(R.id.queue_lock).setVisible(!keepSorted); toolbar.getMenu().findItem(R.id.queue_sort_random).setVisible(!keepSorted); toolbar.getMenu().findItem(R.id.queue_keep_sorted).setChecked(keepSorted); isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), @@ -635,11 +636,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi } @Override - public boolean isItemViewSwipeEnabled() { - return !UserPreferences.isQueueLocked(); - } - - @Override public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); // Check if drag finished diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java index 14f355b52..8bfcfd1ed 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java @@ -25,7 +25,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.FeedDiscoverAdapter; -import de.danoeh.antennapod.core.event.DiscoveryDefaultUpdateEvent; +import de.danoeh.antennapod.event.DiscoveryDefaultUpdateEvent; import de.danoeh.antennapod.discovery.ItunesTopListLoader; import de.danoeh.antennapod.discovery.PodcastSearchResult; import io.reactivex.disposables.Disposable; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java index f8326d9c1..e43b6f314 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SearchFragment.java @@ -1,5 +1,7 @@ package de.danoeh.antennapod.fragment; + +import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -9,6 +11,7 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.ProgressBar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -26,10 +29,10 @@ import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; import de.danoeh.antennapod.adapter.FeedSearchResultAdapter; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.core.storage.FeedSearcher; @@ -70,7 +73,6 @@ public class SearchFragment extends Fragment { private SearchView searchView; private Handler automaticSearchDebouncer; private long lastQueryChange = 0; - /** * Create a new SearchFragment that searches all feeds. */ @@ -153,6 +155,22 @@ public class SearchFragment extends Fragment { if (getArguments().getString(ARG_QUERY, null) != null) { search(); } + searchView.setOnQueryTextFocusChangeListener((view, hasFocus) -> { + if (hasFocus) { + showInputMethod(view.findFocus()); + } + }); + recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { + InputMethodManager imm = (InputMethodManager) + getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(recyclerView.getWindowToken(), 0); + } + } + }); return layout; } @@ -320,4 +338,11 @@ public class SearchFragment extends Fragment { List<Feed> feeds = FeedSearcher.searchFeeds(getContext(), query); return new Pair<>(items, feeds); } + + private void showInputMethod(View view) { + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(view, 0); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java index ea6c2ca0d..c4ac25455 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -33,6 +33,7 @@ import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.Callable; @@ -42,8 +43,8 @@ import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.SubscriptionsRecyclerAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; -import de.danoeh.antennapod.core.event.FeedListUpdateEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.menuhandler.MenuItemUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; @@ -250,8 +251,8 @@ public class SubscriptionFragment extends Fragment } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public void onViewCreated(@NonNull View v, Bundle savedInstanceState) { + super.onViewCreated(v, savedInstanceState); subscriptionAdapter = new SubscriptionsRecyclerAdapter((MainActivity) getActivity()); subscriptionAdapter.setOnSelectModeListener(this); subscriptionRecycler.setAdapter(subscriptionAdapter); @@ -293,9 +294,9 @@ public class SubscriptionFragment extends Fragment NavDrawerData data = DBReader.getNavDrawerData(); List<NavDrawerData.DrawerItem> items = data.items; for (NavDrawerData.DrawerItem item : items) { - if (item.type == NavDrawerData.DrawerItem.Type.FOLDER + if (item.type == NavDrawerData.DrawerItem.Type.TAG && item.getTitle().equals(displayedFolder)) { - return ((NavDrawerData.FolderDrawerItem) item).children; + return ((NavDrawerData.TagDrawerItem) item).children; } } return items; @@ -344,14 +345,15 @@ public class SubscriptionFragment extends Fragment R.string.remove_all_new_flags_confirmation_msg, () -> DBWriter.removeFeedNewFlag(feed.getId())); return true; - } else if (itemId == R.id.add_to_folder) { - TagSettingsDialog.newInstance(feed.getPreferences()).show(getChildFragmentManager(), TagSettingsDialog.TAG); + } else if (itemId == R.id.edit_tags) { + TagSettingsDialog.newInstance(Collections.singletonList(feed.getPreferences())) + .show(getChildFragmentManager(), TagSettingsDialog.TAG); return true; } else if (itemId == R.id.rename_item) { new RenameFeedDialog(getActivity(), feed).show(); return true; } else if (itemId == R.id.remove_item) { - RemoveFeedDialog.show(getContext(), feed, null); + RemoveFeedDialog.show(getContext(), feed); return true; } else if (itemId == R.id.multi_select) { speedDialView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/actions/FeedMultiSelectActionHandler.java b/app/src/main/java/de/danoeh/antennapod/fragment/actions/FeedMultiSelectActionHandler.java index f160b2241..e3dfe8ade 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/actions/FeedMultiSelectActionHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/actions/FeedMultiSelectActionHandler.java @@ -3,19 +3,23 @@ package de.danoeh.antennapod.fragment.actions; import android.util.Log; import androidx.annotation.PluralsRes; +import androidx.appcompat.app.AlertDialog; import androidx.core.util.Consumer; import com.google.android.material.snackbar.Snackbar; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.databinding.PlaybackSpeedFeedSettingDialogBinding; import de.danoeh.antennapod.dialog.RemoveFeedDialog; +import de.danoeh.antennapod.dialog.TagSettingsDialog; import de.danoeh.antennapod.fragment.preferences.dialog.PreferenceListDialog; import de.danoeh.antennapod.fragment.preferences.dialog.PreferenceSwitchDialog; import de.danoeh.antennapod.model.feed.Feed; @@ -33,7 +37,7 @@ public class FeedMultiSelectActionHandler { public void handleAction(int id) { if (id == R.id.remove_item) { - RemoveFeedDialog.show(activity, selectedItems, null); + RemoveFeedDialog.show(activity, selectedItems); } else if (id == R.id.keep_updated) { keepUpdatedPrefHandler(); } else if (id == R.id.autodownload) { @@ -42,6 +46,8 @@ public class FeedMultiSelectActionHandler { autoDeleteEpisodesPrefHandler(); } else if (id == R.id.playback_speed) { playbackSpeedPrefHandler(); + } else if (id == R.id.edit_tags) { + editFeedPrefTags(); } else { Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=" + id); } @@ -64,25 +70,26 @@ public class FeedMultiSelectActionHandler { new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.US)); private void playbackSpeedPrefHandler() { - final String[] speeds = activity.getResources().getStringArray(R.array.playback_speed_values); - String[] values = new String[speeds.length + 1]; - values[0] = SPEED_FORMAT.format(FeedPreferences.SPEED_USE_GLOBAL); - - String[] entries = new String[speeds.length + 1]; - entries[0] = activity.getString(R.string.feed_auto_download_global); - - System.arraycopy(speeds, 0, values, 1, speeds.length); - System.arraycopy(speeds, 0, entries, 1, speeds.length); - - PreferenceListDialog preferenceListDialog = new PreferenceListDialog(activity, - activity.getString(R.string.playback_speed)); - preferenceListDialog.openDialog(entries); - preferenceListDialog.setOnPreferenceChangedListener(pos -> { - saveFeedPreferences(feedPreferences -> { - feedPreferences.setFeedPlaybackSpeed(Float.parseFloat((String) values[pos])); - }); - + PlaybackSpeedFeedSettingDialogBinding viewBinding = + PlaybackSpeedFeedSettingDialogBinding.inflate(activity.getLayoutInflater()); + viewBinding.seekBar.setProgressChangedListener(speed -> + viewBinding.currentSpeedLabel.setText(String.format(Locale.getDefault(), "%.2fx", speed))); + viewBinding.useGlobalCheckbox.setOnCheckedChangeListener((buttonView, isChecked) -> { + viewBinding.seekBar.setEnabled(!isChecked); + viewBinding.seekBar.setAlpha(isChecked ? 0.4f : 1f); + viewBinding.currentSpeedLabel.setAlpha(isChecked ? 0.4f : 1f); }); + viewBinding.seekBar.updateSpeed(1.0f); + new AlertDialog.Builder(activity) + .setTitle(R.string.playback_speed) + .setView(viewBinding.getRoot()) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + float newSpeed = viewBinding.useGlobalCheckbox.isChecked() + ? FeedPreferences.SPEED_USE_GLOBAL : viewBinding.seekBar.getCurrentSpeed(); + saveFeedPreferences(feedPreferences -> feedPreferences.setFeedPlaybackSpeed(newSpeed)); + }) + .setNegativeButton(R.string.cancel_label, null) + .show(); } private void autoDeleteEpisodesPrefHandler() { @@ -136,4 +143,13 @@ public class FeedMultiSelectActionHandler { } showMessage(R.plurals.updated_feeds_batch_label, selectedItems.size()); } + + private void editFeedPrefTags() { + ArrayList<FeedPreferences> preferencesList = new ArrayList<>(); + for (Feed feed : selectedItems) { + preferencesList.add(feed.getPreferences()); + } + TagSettingsDialog.newInstance(preferencesList).show(activity.getSupportFragmentManager(), + TagSettingsDialog.TAG); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java index c813cbf7a..c2c5adc9a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -15,7 +15,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.OnlineFeedViewActivity; import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetServiceException; @@ -76,8 +76,8 @@ public abstract class PodcastListFragment extends Fragment { disposable = Observable.fromCallable( () -> { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(), - GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); + SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(), + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword()); return loadPodcastData(service); }) .subscribeOn(Schedulers.io()) @@ -101,7 +101,7 @@ public abstract class PodcastListFragment extends Fragment { }, error -> { gridView.setVisibility(View.GONE); progressBar.setVisibility(View.GONE); - txtvError.setText(getString(R.string.error_msg_prefix) + error.getMessage()); + txtvError.setText(error.getMessage()); txtvError.setVisibility(View.VISIBLE); butRetry.setVisibility(View.VISIBLE); Log.e(TAG, Log.getStackTraceString(error)); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index f961e30bb..abdfab941 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; import androidx.fragment.app.ListFragment; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetTag; @@ -51,8 +51,8 @@ public class TagListFragment extends ListFragment { disposable = Observable.fromCallable( () -> { GpodnetService service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(), - GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); + SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(), + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword()); return service.getTopTags(COUNT); }) .subscribeOn(Schedulers.io()) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java deleted file mode 100644 index 4fb734e17..000000000 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java +++ /dev/null @@ -1,128 +0,0 @@ -package de.danoeh.antennapod.fragment.preferences; - -import android.app.Activity; -import android.os.Bundle; -import androidx.core.text.HtmlCompat; -import androidx.preference.PreferenceFragmentCompat; - -import android.text.Spanned; -import android.text.format.DateUtils; -import com.google.android.material.snackbar.Snackbar; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.event.SyncServiceEvent; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; -import de.danoeh.antennapod.core.sync.SyncService; -import de.danoeh.antennapod.dialog.AuthenticationDialog; -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; - -public class GpodderPreferencesFragment extends PreferenceFragmentCompat { - private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate"; - private static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; - private static final String PREF_GPODNET_SYNC = "pref_gpodnet_sync"; - private static final String PREF_GPODNET_FORCE_FULL_SYNC = "pref_gpodnet_force_full_sync"; - private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout"; - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - addPreferencesFromResource(R.xml.preferences_gpodder); - setupGpodderScreen(); - } - - @Override - public void onStart() { - super.onStart(); - ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.gpodnet_main_label); - updateGpodnetPreferenceScreen(); - EventBus.getDefault().register(this); - } - - @Override - public void onStop() { - super.onStop(); - EventBus.getDefault().unregister(this); - ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(""); - } - - @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) - public void syncStatusChanged(SyncServiceEvent event) { - updateGpodnetPreferenceScreen(); - if (!GpodnetPreferences.loggedIn()) { - return; - } - if (event.getMessageResId() == R.string.sync_status_error - || event.getMessageResId() == R.string.sync_status_success) { - updateLastGpodnetSyncReport(SyncService.isLastSyncSuccessful(getContext()), - SyncService.getLastSyncAttempt(getContext())); - } else { - ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(event.getMessageResId()); - } - } - - private void setupGpodderScreen() { - final Activity activity = getActivity(); - - findPreference(PREF_GPODNET_LOGIN).setOnPreferenceClickListener(preference -> { - new GpodderAuthenticationFragment().show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG); - return true; - }); - findPreference(PREF_GPODNET_SETLOGIN_INFORMATION) - .setOnPreferenceClickListener(preference -> { - AuthenticationDialog dialog = new AuthenticationDialog(activity, - R.string.pref_gpodnet_setlogin_information_title, false, GpodnetPreferences.getUsername(), - null) { - - @Override - protected void onConfirmed(String username, String password) { - GpodnetPreferences.setPassword(password); - } - }; - dialog.show(); - return true; - }); - findPreference(PREF_GPODNET_SYNC).setOnPreferenceClickListener(preference -> { - SyncService.syncImmediately(getActivity().getApplicationContext()); - return true; - }); - findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> { - SyncService.fullSync(getContext()); - return true; - }); - findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(preference -> { - GpodnetPreferences.logout(); - Snackbar.make(getView(), R.string.pref_gpodnet_logout_toast, Snackbar.LENGTH_LONG).show(); - updateGpodnetPreferenceScreen(); - return true; - }); - } - - private void updateGpodnetPreferenceScreen() { - final boolean loggedIn = GpodnetPreferences.loggedIn(); - findPreference(PREF_GPODNET_LOGIN).setEnabled(!loggedIn); - findPreference(PREF_GPODNET_SETLOGIN_INFORMATION).setEnabled(loggedIn); - findPreference(PREF_GPODNET_SYNC).setEnabled(loggedIn); - findPreference(PREF_GPODNET_FORCE_FULL_SYNC).setEnabled(loggedIn); - findPreference(PREF_GPODNET_LOGOUT).setEnabled(loggedIn); - if (loggedIn) { - String format = getActivity().getString(R.string.pref_gpodnet_login_status); - String summary = String.format(format, GpodnetPreferences.getUsername(), - GpodnetPreferences.getDeviceID()); - Spanned formattedSummary = HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_LEGACY); - findPreference(PREF_GPODNET_LOGOUT).setSummary(formattedSummary); - updateLastGpodnetSyncReport(SyncService.isLastSyncSuccessful(getContext()), - SyncService.getLastSyncAttempt(getContext())); - } else { - findPreference(PREF_GPODNET_LOGOUT).setSummary(null); - } - } - - private void updateLastGpodnetSyncReport(boolean successful, long lastTime) { - String status = String.format("%1$s (%2$s)", getString(successful - ? R.string.gpodnetsync_pref_report_successful : R.string.gpodnetsync_pref_report_failed), - DateUtils.getRelativeDateTimeString(getContext(), - lastTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME)); - ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(status); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java index f6aa45e93..b72d1eb32 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java @@ -12,6 +12,13 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.util.Log; + +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.activity.result.contract.ActivityResultContracts.GetContent; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.content.FileProvider; import androidx.preference.PreferenceFragmentCompat; @@ -54,13 +61,19 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { private static final String DEFAULT_HTML_OUTPUT_NAME = "antennapod-feeds-%s.html"; private static final String CONTENT_TYPE_HTML = "text/html"; private static final String DEFAULT_FAVORITES_OUTPUT_NAME = "antennapod-favorites-%s.html"; - private static final int REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH = 1; - private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 2; - private static final int REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH = 3; - private static final int REQUEST_CODE_RESTORE_DATABASE = 4; - private static final int REQUEST_CODE_BACKUP_DATABASE = 5; - private static final int REQUEST_CODE_CHOOSE_FAVORITES_EXPORT_PATH = 6; private static final String DATABASE_EXPORT_FILENAME = "AntennaPodBackup-%s.db"; + private final ActivityResultLauncher<Intent> chooseOpmlExportPathLauncher = + registerForActivityResult(new StartActivityForResult(), this::chooseOpmlExportPathResult); + private final ActivityResultLauncher<Intent> chooseHtmlExportPathLauncher = + registerForActivityResult(new StartActivityForResult(), this::chooseHtmlExportPathResult); + private final ActivityResultLauncher<Intent> chooseFavoritesExportPathLauncher = + registerForActivityResult(new StartActivityForResult(), this::chooseFavoritesExportPathResult); + private final ActivityResultLauncher<Intent> restoreDatabaseLauncher = + registerForActivityResult(new StartActivityForResult(), this::restoreDatabaseResult); + private final ActivityResultLauncher<String> backupDatabaseLauncher = + registerForActivityResult(new BackupDatabase(), this::backupDatabaseResult); + private final ActivityResultLauncher<String> chooseOpmlImportPathLauncher = + registerForActivityResult(new GetContent(), this::chooseOpmlImportPathResult); private Disposable disposable; private ProgressDialog progressDialog; @@ -95,23 +108,20 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener( preference -> { openExportPathPicker(CONTENT_TYPE_OPML, dateStampFilename(DEFAULT_OPML_OUTPUT_NAME), - REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH, new OpmlWriter()); + chooseOpmlExportPathLauncher, new OpmlWriter()); return true; } ); findPreference(PREF_HTML_EXPORT).setOnPreferenceClickListener( preference -> { openExportPathPicker(CONTENT_TYPE_HTML, dateStampFilename(DEFAULT_HTML_OUTPUT_NAME), - REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH, new HtmlWriter()); + chooseHtmlExportPathLauncher, new HtmlWriter()); return true; }); findPreference(PREF_OPML_IMPORT).setOnPreferenceClickListener( preference -> { try { - Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT); - intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE); - intentGetContentAction.setType("*/*"); - startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); + chooseOpmlImportPathLauncher.launch("*/*"); } catch (ActivityNotFoundException e) { Log.e(TAG, "No activity found. Should never happen..."); } @@ -130,7 +140,7 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { findPreference(PREF_FAVORITE_EXPORT).setOnPreferenceClickListener( preference -> { openExportPathPicker(CONTENT_TYPE_HTML, dateStampFilename(DEFAULT_FAVORITES_OUTPUT_NAME), - REQUEST_CODE_CHOOSE_FAVORITES_EXPORT_PATH, new FavoritesWriter()); + chooseFavoritesExportPathLauncher, new FavoritesWriter()); return true; }); } @@ -160,12 +170,7 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { private void exportDatabase() { if (Build.VERSION.SDK_INT >= 19) { - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType("application/x-sqlite3") - .putExtra(Intent.EXTRA_TITLE, dateStampFilename(DATABASE_EXPORT_FILENAME)); - - startActivityForResult(intent, REQUEST_CODE_BACKUP_DATABASE); + backupDatabaseLauncher.launch(dateStampFilename(DATABASE_EXPORT_FILENAME)); } else { File sd = Environment.getExternalStorageDirectory(); File backupDB = new File(sd, dateStampFilename(DATABASE_EXPORT_FILENAME)); @@ -190,18 +195,10 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { // add a button builder.setNegativeButton(R.string.no, null); builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { - if (Build.VERSION.SDK_INT >= 19) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.setType("*/*"); - startActivityForResult(intent, REQUEST_CODE_RESTORE_DATABASE); - } else { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - startActivityForResult(Intent.createChooser(intent, - getString(R.string.import_select_file)), REQUEST_CODE_RESTORE_DATABASE); - } - } - ); + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.setType("*/*"); + restoreDatabaseLauncher.launch(intent); + }); // create and show the alert dialog builder.show(); @@ -227,15 +224,14 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { sendIntent.putExtra(Intent.EXTRA_STREAM, streamUri); sendIntent.setType("text/plain"); sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - List<ResolveInfo> resInfoList = getContext().getPackageManager() - .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } + Intent chooserIntent = Intent.createChooser(sendIntent, getString(R.string.send_label)); + List<ResolveInfo> resInfoList = getContext().getPackageManager() + .queryIntentActivities(sendIntent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + getContext().grantUriPermission(packageName, streamUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } - getContext().startActivity(Intent.createChooser(sendIntent, getString(R.string.send_label))); + getContext().startActivity(chooserIntent); }); alert.create().show(); } @@ -249,64 +245,97 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { alert.show(); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode != Activity.RESULT_OK || data == null) { + private void chooseOpmlExportPathResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { return; } - Uri uri = data.getData(); + final Uri uri = result.getData().getData(); + exportWithWriter(new OpmlWriter(), uri); + } - if (requestCode == REQUEST_CODE_CHOOSE_OPML_EXPORT_PATH) { - exportWithWriter(new OpmlWriter(), uri); - } else if (requestCode == REQUEST_CODE_CHOOSE_HTML_EXPORT_PATH) { - exportWithWriter(new HtmlWriter(), uri); - } else if (requestCode == REQUEST_CODE_CHOOSE_FAVORITES_EXPORT_PATH) { - exportWithWriter(new FavoritesWriter(), uri); - } else if (requestCode == REQUEST_CODE_RESTORE_DATABASE) { - progressDialog.show(); - disposable = Completable.fromAction(() -> DatabaseExporter.importBackup(uri, getContext())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { - showDatabaseImportSuccessDialog(); - UserPreferences.unsetUsageCountingDate(); - progressDialog.dismiss(); - }, this::showExportErrorDialog); - } else if (requestCode == REQUEST_CODE_BACKUP_DATABASE) { - progressDialog.show(); - disposable = Completable.fromAction(() -> DatabaseExporter.exportToDocument(uri, getContext())) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { - Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG).show(); - progressDialog.dismiss(); - }, this::showExportErrorDialog); - } else if (requestCode == REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH) { - Intent intent = new Intent(getContext(), OpmlImportActivity.class); - intent.setData(uri); - startActivity(intent); + private void chooseHtmlExportPathResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { + return; } + final Uri uri = result.getData().getData(); + exportWithWriter(new HtmlWriter(), uri); } - private void openExportPathPicker(String contentType, String title, int requestCode, ExportWriter writer) { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN_MR2) { - Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .setType(contentType) - .putExtra(Intent.EXTRA_TITLE, title); + private void chooseFavoritesExportPathResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { + return; + } + final Uri uri = result.getData().getData(); + exportWithWriter(new FavoritesWriter(), uri); + } - // Creates an implicit intent to launch a file manager which lets - // the user choose a specific directory to export to. - try { - startActivityForResult(intentPickAction, requestCode); - return; - } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); - } + private void restoreDatabaseResult(final ActivityResult result) { + if (result.getResultCode() != Activity.RESULT_OK || result.getData() == null) { + return; + } + final Uri uri = result.getData().getData(); + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.importBackup(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + showDatabaseImportSuccessDialog(); + UserPreferences.unsetUsageCountingDate(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } + + private void backupDatabaseResult(final Uri uri) { + if (uri == null) { + return; + } + progressDialog.show(); + disposable = Completable.fromAction(() -> DatabaseExporter.exportToDocument(uri, getContext())) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + Snackbar.make(getView(), R.string.export_success_title, Snackbar.LENGTH_LONG).show(); + progressDialog.dismiss(); + }, this::showExportErrorDialog); + } + + private void chooseOpmlImportPathResult(final Uri uri) { + if (uri == null) { + return; + } + final Intent intent = new Intent(getContext(), OpmlImportActivity.class); + intent.setData(uri); + startActivity(intent); + } + + private void openExportPathPicker(String contentType, String title, + final ActivityResultLauncher<Intent> result, ExportWriter writer) { + Intent intentPickAction = new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(contentType) + .putExtra(Intent.EXTRA_TITLE, title); + + // Creates an implicit intent to launch a file manager which lets + // the user choose a specific directory to export to. + try { + result.launch(intentPickAction); + return; + } catch (ActivityNotFoundException e) { + Log.e(TAG, "No activity found. Should never happen..."); } // If we are using a SDK lower than API 21 or the implicit intent failed // fallback to the legacy export process exportWithWriter(writer, null); } + + private static class BackupDatabase extends ActivityResultContracts.CreateDocument { + @NonNull + @Override + public Intent createIntent(@NonNull final Context context, @NonNull final String input) { + return super.createIntent(context, input) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("application/x-sqlite3"); + } + } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index cc09acbca..891d3737b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -1,6 +1,8 @@ package de.danoeh.antennapod.fragment.preferences; import android.content.Intent; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; @@ -17,12 +19,11 @@ import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.fragment.preferences.about.AboutFragment; public class MainPreferencesFragment extends PreferenceFragmentCompat { - private static final String TAG = "MainPreferencesFragment"; private static final String PREF_SCREEN_USER_INTERFACE = "prefScreenInterface"; private static final String PREF_SCREEN_PLAYBACK = "prefScreenPlayback"; private static final String PREF_SCREEN_NETWORK = "prefScreenNetwork"; - private static final String PREF_SCREEN_GPODDER = "prefScreenGpodder"; + private static final String PREF_SCREEN_SYNCHRONIZATION = "prefScreenSynchronization"; private static final String PREF_SCREEN_STORAGE = "prefScreenStorage"; private static final String PREF_DOCUMENTATION = "prefDocumentation"; private static final String PREF_VIEW_FORUM = "prefViewForum"; @@ -43,15 +44,26 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { // and afterwards remove the following lines. Please keep in mind that AntennaPod is licensed under the GPL. // This means that your application needs to be open-source under the GPL, too. // It must also include a prominent copyright notice. - String packageName = getContext().getPackageName(); - if (!"de.danoeh.antennapod".equals(packageName) && !"de.danoeh.antennapod.debug".equals(packageName)) { + int packageHash = getContext().getPackageName().hashCode(); + if (packageHash != 1790437538 && packageHash != -1190467065) { findPreference(PREF_CATEGORY_PROJECT).setVisible(false); Preference copyrightNotice = new Preference(getContext()); + copyrightNotice.setIcon(R.drawable.ic_info_white); + copyrightNotice.getIcon().mutate() + .setColorFilter(new PorterDuffColorFilter(0xffcc0000, PorterDuff.Mode.MULTIPLY)); copyrightNotice.setSummary("This application is based on AntennaPod." + " The AntennaPod team does NOT provide support for this unofficial version." + " If you can read this message, the developers of this modification" + " violate the GNU General Public License (GPL)."); findPreference(PREF_CATEGORY_PROJECT).getParent().addPreference(copyrightNotice); + } else if (packageHash == -1190467065) { + Preference debugNotice = new Preference(getContext()); + debugNotice.setIcon(R.drawable.ic_info_white); + debugNotice.getIcon().mutate() + .setColorFilter(new PorterDuffColorFilter(0xffcc0000, PorterDuff.Mode.MULTIPLY)); + debugNotice.setOrder(-1); + debugNotice.setSummary("This is a development version of AntennaPod and not meant for daily use"); + findPreference(PREF_CATEGORY_PROJECT).getParent().addPreference(debugNotice); } } @@ -74,8 +86,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_network); return true; }); - findPreference(PREF_SCREEN_GPODDER).setOnPreferenceClickListener(preference -> { - ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_gpodder); + findPreference(PREF_SCREEN_SYNCHRONIZATION).setOnPreferenceClickListener(preference -> { + ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_synchronization); return true; }); findPreference(PREF_SCREEN_STORAGE).setOnPreferenceClickListener(preference -> { @@ -142,8 +154,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_network)) .addBreadcrumb(R.string.automation) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_autodownload)); - config.index(R.xml.preferences_gpodder) - .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_gpodder)); + config.index(R.xml.preferences_synchronization) + .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_synchronization)); config.index(R.xml.preferences_notifications) .addBreadcrumb(PreferenceActivity.getTitleOfPage(R.xml.preferences_notifications)); config.index(R.xml.feed_settings) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NotificationPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NotificationPreferencesFragment.java index 94e151f7a..ba17cedb2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NotificationPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NotificationPreferencesFragment.java @@ -4,11 +4,10 @@ import android.os.Bundle; import androidx.preference.PreferenceFragmentCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; public class NotificationPreferencesFragment extends PreferenceFragmentCompat { - private static final String TAG = "NotificationPrefFragment"; private static final String PREF_GPODNET_NOTIFICATIONS = "pref_gpodnet_notifications"; @Override @@ -24,7 +23,6 @@ public class NotificationPreferencesFragment extends PreferenceFragmentCompat { } private void setUpScreen() { - final boolean loggedIn = GpodnetPreferences.loggedIn(); - findPreference(PREF_GPODNET_NOTIFICATIONS).setEnabled(loggedIn); + findPreference(PREF_GPODNET_NOTIFICATIONS).setEnabled(SynchronizationSettings.isProviderConnected()); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java index 1fa1fed58..7fa2ed4d1 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackPreferencesFragment.java @@ -10,13 +10,12 @@ import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.preferences.UsageStatistics; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil; import de.danoeh.antennapod.dialog.SkipPreferenceDialog; import de.danoeh.antennapod.dialog.VariableSpeedDialog; -import de.danoeh.antennapod.preferences.PreferenceControllerFlavorHelper; import java.util.Map; import org.greenrobot.eventbus.EventBus; @@ -31,7 +30,6 @@ public class PlaybackPreferencesFragment extends PreferenceFragmentCompat { addPreferencesFromResource(R.xml.preferences_playback); setupPlaybackScreen(); - PreferenceControllerFlavorHelper.setupFlavoredUI(this); buildSmartMarkAsPlayedPreference(); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java index 208ede8cc..04324f709 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java @@ -68,7 +68,7 @@ public class PlaybackStatisticsFragment extends Fragment { View root = inflater.inflate(R.layout.statistics_activity, container, false); feedStatisticsList = root.findViewById(R.id.statistics_list); progressBar = root.findViewById(R.id.progressBar); - listAdapter = new PlaybackStatisticsListAdapter(getContext()); + listAdapter = new PlaybackStatisticsListAdapter(this); listAdapter.setCountAll(countAll); feedStatisticsList.setLayoutManager(new LinearLayoutManager(getContext())); feedStatisticsList.setAdapter(listAdapter); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java index 04b9677e2..ff974179e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java @@ -10,8 +10,8 @@ import androidx.preference.PreferenceFragmentCompat; import android.widget.ListView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; import de.danoeh.antennapod.dialog.FeedSortDialog; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/GpodderAuthenticationFragment.java index c0bf3e0ea..9dfe6840c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/GpodderAuthenticationFragment.java @@ -1,4 +1,4 @@ -package de.danoeh.antennapod.fragment.preferences; +package de.danoeh.antennapod.fragment.preferences.synchronization; import android.app.Dialog; import android.content.Context; @@ -15,30 +15,35 @@ import android.widget.ProgressBar; import android.widget.RadioGroup; import android.widget.TextView; import android.widget.ViewFlipper; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; + import com.google.android.material.button.MaterialButton; import com.google.android.material.textfield.TextInputLayout; + +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.core.sync.SyncService; -import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; -import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetDevice; +import de.danoeh.antennapod.core.sync.SynchronizationProviderViewData; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; import de.danoeh.antennapod.core.util.FileNameGenerator; import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.net.sync.gpoddernet.GpodnetService; +import de.danoeh.antennapod.net.sync.gpoddernet.model.GpodnetDevice; import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; -import java.util.List; -import java.util.Locale; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - /** * Guides the user through the authentication process. */ @@ -83,23 +88,24 @@ public class GpodderAuthenticationFragment extends DialogFragment { final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup); final EditText serverUrlText = view.findViewById(R.id.serverUrlText); - if (!GpodnetService.DEFAULT_BASE_HOST.equals(GpodnetPreferences.getHosturl())) { - serverUrlText.setText(GpodnetPreferences.getHosturl()); + if (!GpodnetService.DEFAULT_BASE_HOST.equals(SynchronizationCredentials.getHosturl())) { + serverUrlText.setText(SynchronizationCredentials.getHosturl()); } final TextInputLayout serverUrlTextInput = view.findViewById(R.id.serverUrlTextInput); serverRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { serverUrlTextInput.setVisibility(checkedId == R.id.customServerRadio ? View.VISIBLE : View.GONE); }); selectHost.setOnClickListener(v -> { + SynchronizationCredentials.clear(getContext()); if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) { - GpodnetPreferences.setHosturl(serverUrlText.getText().toString()); + SynchronizationCredentials.setHosturl(serverUrlText.getText().toString()); } else { - GpodnetPreferences.setHosturl(GpodnetService.DEFAULT_BASE_HOST); + SynchronizationCredentials.setHosturl(GpodnetService.DEFAULT_BASE_HOST); } service = new GpodnetService(AntennapodHttpClient.getHttpClient(), - GpodnetPreferences.getHosturl(), GpodnetPreferences.getDeviceID(), - GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword()); - getDialog().setTitle(GpodnetPreferences.getHosturl()); + SynchronizationCredentials.getHosturl(), SynchronizationCredentials.getDeviceID(), + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getPassword()); + getDialog().setTitle(SynchronizationCredentials.getHosturl()); advance(); }); } @@ -116,7 +122,7 @@ public class GpodderAuthenticationFragment extends DialogFragment { createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/")); - if (GpodnetPreferences.getHosturl().startsWith("http://")) { + if (SynchronizationCredentials.getHosturl().startsWith("http://")) { createAccountWarning.setVisibility(View.VISIBLE); } password.setOnEditorActionListener((v, actionID, event) -> @@ -265,15 +271,8 @@ public class GpodderAuthenticationFragment extends DialogFragment { }); } - private void writeLoginCredentials() { - GpodnetPreferences.setUsername(username); - GpodnetPreferences.setPassword(password); - GpodnetPreferences.setDeviceID(selectedDevice.getId()); - } - private void advance() { if (currentStep < STEP_FINISH) { - View view = viewFlipper.getChildAt(currentStep + 1); if (currentStep == STEP_DEFAULT) { setupHostView(view); @@ -289,7 +288,10 @@ public class GpodderAuthenticationFragment extends DialogFragment { if (selectedDevice == null) { throw new IllegalStateException("Device must not be null here"); } else { - writeLoginCredentials(); + SynchronizationSettings.setSelectedSyncProvider(SynchronizationProviderViewData.GPODDER_NET); + SynchronizationCredentials.setUsername(username); + SynchronizationCredentials.setPassword(password); + SynchronizationCredentials.setDeviceID(selectedDevice.getId()); setupFinishView(view); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/NextcloudAuthenticationFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/NextcloudAuthenticationFragment.java new file mode 100644 index 000000000..2e9260c1d --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/NextcloudAuthenticationFragment.java @@ -0,0 +1,92 @@ +package de.danoeh.antennapod.fragment.preferences.synchronization; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; +import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; +import de.danoeh.antennapod.core.sync.SynchronizationProviderViewData; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; +import de.danoeh.antennapod.databinding.NextcloudAuthDialogBinding; +import de.danoeh.antennapod.net.sync.nextcloud.NextcloudLoginFlow; + +/** + * Guides the user through the authentication process. + */ +public class NextcloudAuthenticationFragment extends DialogFragment + implements NextcloudLoginFlow.AuthenticationCallback { + public static final String TAG = "NextcloudAuthenticationFragment"; + private NextcloudAuthDialogBinding viewBinding; + private NextcloudLoginFlow nextcloudLoginFlow; + private boolean shouldDismiss = false; + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder dialog = new AlertDialog.Builder(getContext()); + dialog.setTitle(R.string.gpodnetauth_login_butLabel); + dialog.setNegativeButton(R.string.cancel_label, null); + dialog.setCancelable(false); + this.setCancelable(false); + + viewBinding = NextcloudAuthDialogBinding.inflate(getLayoutInflater()); + dialog.setView(viewBinding.getRoot()); + + viewBinding.loginButton.setOnClickListener(v -> { + viewBinding.errorText.setVisibility(View.GONE); + viewBinding.loginButton.setVisibility(View.GONE); + viewBinding.loginProgressContainer.setVisibility(View.VISIBLE); + nextcloudLoginFlow = new NextcloudLoginFlow(AntennapodHttpClient.getHttpClient(), + viewBinding.serverUrlText.getText().toString(), getContext(), this); + nextcloudLoginFlow.start(); + }); + + return dialog.create(); + } + + @Override + public void onDismiss(@NonNull DialogInterface dialog) { + super.onDismiss(dialog); + if (nextcloudLoginFlow != null) { + nextcloudLoginFlow.cancel(); + } + } + + @Override + public void onResume() { + super.onResume(); + if (shouldDismiss) { + dismiss(); + } + } + + @Override + public void onNextcloudAuthenticated(String server, String username, String password) { + SynchronizationSettings.setSelectedSyncProvider(SynchronizationProviderViewData.NEXTCLOUD_GPODDER); + SynchronizationCredentials.clear(getContext()); + SynchronizationCredentials.setPassword(password); + SynchronizationCredentials.setHosturl(server); + SynchronizationCredentials.setUsername(username); + SyncService.fullSync(getContext()); + if (isVisible()) { + dismiss(); + } else { + shouldDismiss = true; + } + } + + @Override + public void onNextcloudAuthError(String errorMessage) { + viewBinding.loginProgressContainer.setVisibility(View.GONE); + viewBinding.errorText.setVisibility(View.VISIBLE); + viewBinding.errorText.setText(errorMessage); + viewBinding.loginButton.setVisibility(View.VISIBLE); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/SynchronizationPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/SynchronizationPreferencesFragment.java new file mode 100644 index 000000000..8cb7f45db --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/synchronization/SynchronizationPreferencesFragment.java @@ -0,0 +1,222 @@ +package de.danoeh.antennapod.fragment.preferences.synchronization; + +import android.app.Activity; +import android.os.Bundle; +import android.text.Spanned; +import android.text.format.DateUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.core.text.HtmlCompat; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; + +import com.google.android.material.snackbar.Snackbar; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.event.SyncServiceEvent; +import de.danoeh.antennapod.core.sync.SynchronizationCredentials; +import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.SynchronizationProviderViewData; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; +import de.danoeh.antennapod.dialog.AuthenticationDialog; + +public class SynchronizationPreferencesFragment extends PreferenceFragmentCompat { + private static final String PREFERENCE_SYNCHRONIZATION_DESCRIPTION = "preference_synchronization_description"; + private static final String PREFERENCE_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information"; + private static final String PREFERENCE_SYNC = "pref_synchronization_sync"; + private static final String PREFERENCE_FORCE_FULL_SYNC = "pref_synchronization_force_full_sync"; + private static final String PREFERENCE_LOGOUT = "pref_synchronization_logout"; + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + addPreferencesFromResource(R.xml.preferences_synchronization); + setupScreen(); + updateScreen(); + } + + @Override + public void onStart() { + super.onStart(); + ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.synchronization_pref); + updateScreen(); + EventBus.getDefault().register(this); + } + + @Override + public void onStop() { + super.onStop(); + EventBus.getDefault().unregister(this); + ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(""); + } + + @Subscribe(threadMode = ThreadMode.MAIN, sticky = true) + public void syncStatusChanged(SyncServiceEvent event) { + if (!SynchronizationSettings.isProviderConnected()) { + return; + } + updateScreen(); + if (event.getMessageResId() == R.string.sync_status_error + || event.getMessageResId() == R.string.sync_status_success) { + updateLastSyncReport(SynchronizationSettings.isLastSyncSuccessful(), + SynchronizationSettings.getLastSyncAttempt()); + } else { + ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(event.getMessageResId()); + } + } + + private void setupScreen() { + final Activity activity = getActivity(); + findPreference(PREFERENCE_GPODNET_SETLOGIN_INFORMATION) + .setOnPreferenceClickListener(preference -> { + AuthenticationDialog dialog = new AuthenticationDialog(activity, + R.string.pref_gpodnet_setlogin_information_title, + false, SynchronizationCredentials.getUsername(), null) { + @Override + protected void onConfirmed(String username, String password) { + SynchronizationCredentials.setPassword(password); + } + }; + dialog.show(); + return true; + }); + findPreference(PREFERENCE_SYNC).setOnPreferenceClickListener(preference -> { + SyncService.syncImmediately(getActivity().getApplicationContext()); + return true; + }); + findPreference(PREFERENCE_FORCE_FULL_SYNC).setOnPreferenceClickListener(preference -> { + SyncService.fullSync(getContext()); + return true; + }); + findPreference(PREFERENCE_LOGOUT).setOnPreferenceClickListener(preference -> { + SynchronizationCredentials.clear(getContext()); + Snackbar.make(getView(), R.string.pref_synchronization_logout_toast, Snackbar.LENGTH_LONG).show(); + SynchronizationSettings.setSelectedSyncProvider(null); + updateScreen(); + return true; + }); + } + + private void updateScreen() { + final boolean loggedIn = SynchronizationSettings.isProviderConnected(); + Preference preferenceHeader = findPreference(PREFERENCE_SYNCHRONIZATION_DESCRIPTION); + if (loggedIn) { + SynchronizationProviderViewData selectedProvider = + SynchronizationProviderViewData.fromIdentifier(getSelectedSyncProviderKey()); + preferenceHeader.setTitle(""); + preferenceHeader.setSummary(selectedProvider.getSummaryResource()); + preferenceHeader.setIcon(selectedProvider.getIconResource()); + preferenceHeader.setOnPreferenceClickListener(null); + } else { + preferenceHeader.setTitle(R.string.synchronization_choose_title); + preferenceHeader.setSummary(R.string.synchronization_summary_unchoosen); + preferenceHeader.setIcon(R.drawable.ic_cloud); + preferenceHeader.setOnPreferenceClickListener((preference) -> { + chooseProviderAndLogin(); + return true; + }); + } + + Preference gpodnetSetLoginPreference = findPreference(PREFERENCE_GPODNET_SETLOGIN_INFORMATION); + gpodnetSetLoginPreference.setVisible(isProviderSelected(SynchronizationProviderViewData.GPODDER_NET)); + gpodnetSetLoginPreference.setEnabled(loggedIn); + findPreference(PREFERENCE_SYNC).setEnabled(loggedIn); + findPreference(PREFERENCE_FORCE_FULL_SYNC).setEnabled(loggedIn); + findPreference(PREFERENCE_LOGOUT).setEnabled(loggedIn); + if (loggedIn) { + String summary = getString(R.string.synchronization_login_status, + SynchronizationCredentials.getUsername(), SynchronizationCredentials.getHosturl()); + Spanned formattedSummary = HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_LEGACY); + findPreference(PREFERENCE_LOGOUT).setSummary(formattedSummary); + updateLastSyncReport(SynchronizationSettings.isLastSyncSuccessful(), + SynchronizationSettings.getLastSyncAttempt()); + } else { + findPreference(PREFERENCE_LOGOUT).setSummary(null); + ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(null); + } + } + + private void chooseProviderAndLogin() { + AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + builder.setTitle(R.string.dialog_choose_sync_service_title); + + SynchronizationProviderViewData[] providers = SynchronizationProviderViewData.values(); + ListAdapter adapter = new ArrayAdapter<SynchronizationProviderViewData>( + getContext(), R.layout.alertdialog_sync_provider_chooser, providers) { + + ViewHolder holder; + + class ViewHolder { + ImageView icon; + TextView title; + } + + public View getView(int position, View convertView, ViewGroup parent) { + final LayoutInflater inflater = LayoutInflater.from(getContext()); + if (convertView == null) { + convertView = inflater.inflate( + R.layout.alertdialog_sync_provider_chooser, null); + + holder = new ViewHolder(); + holder.icon = (ImageView) convertView.findViewById(R.id.icon); + holder.title = (TextView) convertView.findViewById(R.id.title); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + SynchronizationProviderViewData synchronizationProviderViewData = getItem(position); + holder.title.setText(synchronizationProviderViewData.getSummaryResource()); + holder.icon.setImageResource(synchronizationProviderViewData.getIconResource()); + return convertView; + } + }; + + builder.setAdapter(adapter, (dialog, which) -> { + switch (providers[which]) { + case GPODDER_NET: + new GpodderAuthenticationFragment() + .show(getChildFragmentManager(), GpodderAuthenticationFragment.TAG); + break; + case NEXTCLOUD_GPODDER: + new NextcloudAuthenticationFragment() + .show(getChildFragmentManager(), NextcloudAuthenticationFragment.TAG); + break; + default: + break; + } + updateScreen(); + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private boolean isProviderSelected(@NonNull SynchronizationProviderViewData provider) { + String selectedSyncProviderKey = getSelectedSyncProviderKey(); + return provider.getIdentifier().equals(selectedSyncProviderKey); + } + + private String getSelectedSyncProviderKey() { + return SynchronizationSettings.getSelectedSyncProviderKey(); + } + + private void updateLastSyncReport(boolean successful, long lastTime) { + String status = String.format("%1$s (%2$s)", getString(successful + ? R.string.gpodnetsync_pref_report_successful : R.string.gpodnetsync_pref_report_failed), + DateUtils.getRelativeDateTimeString(getContext(), + lastTime, DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, DateUtils.FORMAT_SHOW_TIME)); + ((PreferenceActivity) getActivity()).getSupportActionBar().setSubtitle(status); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java index 50c7c1ae5..adf133856 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/swipeactions/SwipeActions.java @@ -201,12 +201,12 @@ public class SwipeActions extends ItemTouchHelper.SimpleCallback implements Life @Override public float getSwipeEscapeVelocity(float defaultValue) { - return swipeOutEnabled ? defaultValue : Float.MAX_VALUE; + return swipeOutEnabled ? defaultValue * 1.5f : Float.MAX_VALUE; } @Override public float getSwipeVelocityThreshold(float defaultValue) { - return swipeOutEnabled ? defaultValue : 0; + return swipeOutEnabled ? defaultValue * 0.6f : 0; } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index c272af7d5..23fdb86de 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -13,12 +13,12 @@ import com.google.android.material.snackbar.Snackbar; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.preferences.GpodnetPreferences; 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.storage.DBWriter; -import de.danoeh.antennapod.core.sync.SyncService; +import de.danoeh.antennapod.core.sync.SynchronizationSettings; +import de.danoeh.antennapod.core.sync.queue.SynchronizationQueueSink; import de.danoeh.antennapod.core.util.FeedItemUtil; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.ShareUtils; @@ -151,7 +151,7 @@ public class FeedItemMenuHandler { } else if (menuItemId == R.id.mark_read_item) { selectedItem.setPlayed(true); DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, true); - if (GpodnetPreferences.loggedIn()) { + if (SynchronizationSettings.isProviderConnected()) { FeedMedia media = selectedItem.getMedia(); // not all items have media, Gpodder only cares about those that do if (media != null) { @@ -161,17 +161,17 @@ public class FeedItemMenuHandler { .position(media.getDuration() / 1000) .total(media.getDuration() / 1000) .build(); - SyncService.enqueueEpisodeAction(context, actionPlay); + SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, actionPlay); } } } else if (menuItemId == R.id.mark_unread_item) { selectedItem.setPlayed(false); DBWriter.markItemPlayed(selectedItem, FeedItem.UNPLAYED, false); - if (GpodnetPreferences.loggedIn() && selectedItem.getMedia() != null) { + if (selectedItem.getMedia() != null) { EpisodeAction actionNew = new EpisodeAction.Builder(selectedItem, EpisodeAction.NEW) .currentTimestamp() .build(); - SyncService.enqueueEpisodeAction(context, actionNew); + SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, actionNew); } } else if (menuItemId == R.id.add_to_queue_item) { DBWriter.addQueueItem(context, selectedItem); diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index 84c738632..af35bbac9 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -108,9 +108,12 @@ public class PreferenceUpgrader { } } if (oldVersion < 2040000) { - SharedPreferences prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE); - prefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG, + SharedPreferences swipePrefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE); + swipePrefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG, SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE).apply(); } + if (oldVersion < 2050000) { + prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true).apply(); + } } } diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java index 1075117dd..2ea15005a 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java @@ -22,7 +22,7 @@ public class ConnectivityActionReceiver extends BroadcastReceiver { Log.d(TAG, "Received intent"); ClientConfig.initialize(context); - if (NetworkUtils.autodownloadNetworkAvailable()) { + if (NetworkUtils.isAutoDownloadAllowed()) { Log.d(TAG, "auto-dl network available, starting auto-download"); DBTasks.autodownloadUndownloadedItems(context); } else { // if new network is Wi-Fi, finish ongoing downloads, diff --git a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java b/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java index c75164a74..33f0d47b8 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java +++ b/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedSeekBar.java @@ -9,11 +9,9 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Consumer; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.util.playback.PlaybackController; public class PlaybackSpeedSeekBar extends FrameLayout { private SeekBar seekBar; - private PlaybackController controller; private Consumer<Float> progressChangedListener; public PlaybackSpeedSeekBar(@NonNull Context context) { @@ -40,15 +38,9 @@ public class PlaybackSpeedSeekBar extends FrameLayout { seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (controller != null) { - float playbackSpeed = (progress + 10) / 20.0f; - controller.setPlaybackSpeed(playbackSpeed); - - if (progressChangedListener != null) { - progressChangedListener.accept(playbackSpeed); - } - } else if (fromUser) { - seekBar.post(() -> updateSpeed()); + float playbackSpeed = (progress + 10) / 20.0f; + if (progressChangedListener != null) { + progressChangedListener.accept(playbackSpeed); } } @@ -62,21 +54,23 @@ public class PlaybackSpeedSeekBar extends FrameLayout { }); } - public void updateSpeed() { - if (controller != null) { - seekBar.setProgress(Math.round((20 * controller.getCurrentPlaybackSpeedMultiplier()) - 10)); - } - } - - public void setController(PlaybackController controller) { - this.controller = controller; - updateSpeed(); - if (progressChangedListener != null && controller != null) { - progressChangedListener.accept(controller.getCurrentPlaybackSpeedMultiplier()); - } + public void updateSpeed(float speedMultiplier) { + seekBar.setProgress(Math.round((20 * speedMultiplier) - 10)); } public void setProgressChangedListener(Consumer<Float> progressChangedListener) { this.progressChangedListener = progressChangedListener; } + + public float getCurrentSpeed() { + return (seekBar.getProgress() + 10) / 20.0f; + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + seekBar.setEnabled(enabled); + findViewById(R.id.butDecSpeed).setEnabled(enabled); + findViewById(R.id.butIncSpeed).setEnabled(enabled); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java index cd3af5003..8d1810ecb 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java +++ b/app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java @@ -21,7 +21,7 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.CoverLoader; import de.danoeh.antennapod.adapter.actionbutton.ItemActionButton; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.core.util.DateFormatter; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; diff --git a/app/src/main/res/layout/alertdialog_sync_provider_chooser.xml b/app/src/main/res/layout/alertdialog_sync_provider_chooser.xml new file mode 100644 index 000000000..9b4d62804 --- /dev/null +++ b/app/src/main/res/layout/alertdialog_sync_provider_chooser.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:padding="16dp"> + + <ImageView + android:id="@+id/icon" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_marginRight="16dip" + android:layout_marginEnd="16dip" + android:layout_gravity="center_vertical" /> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="" + android:layout_gravity="center" /> + +</LinearLayout> diff --git a/app/src/main/res/layout/audioplayer_fragment.xml b/app/src/main/res/layout/audioplayer_fragment.xml index f801930f5..7efbd23c8 100644 --- a/app/src/main/res/layout/audioplayer_fragment.xml +++ b/app/src/main/res/layout/audioplayer_fragment.xml @@ -12,6 +12,7 @@ android:minHeight="?attr/actionBarSize" android:theme="?attr/actionBarTheme" android:layout_alignParentTop="true" + app:navigationContentDescription="@string/toolbar_back_button_content_description" app:navigationIcon="?homeAsUpIndicator" android:id="@+id/toolbar"/> diff --git a/app/src/main/res/layout/edit_tags_dialog.xml b/app/src/main/res/layout/edit_tags_dialog.xml index 57e3c412f..9ac0b60d3 100644 --- a/app/src/main/res/layout/edit_tags_dialog.xml +++ b/app/src/main/res/layout/edit_tags_dialog.xml @@ -7,6 +7,16 @@ android:orientation="vertical" android:padding="16dp"> + <com.joanzapata.iconify.widget.IconTextView + android:id="@+id/commonTagsInfo" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:visibility="gone" + android:textSize="@dimen/text_size_micro" + android:paddingBottom="16dp" + android:text="@string/multi_feed_common_tags_info" /> + <androidx.recyclerview.widget.RecyclerView android:id="@+id/tagsRecycler" android:layout_width="match_parent" diff --git a/app/src/main/res/layout/episode_filter_dialog.xml b/app/src/main/res/layout/episode_filter_dialog.xml index 9661a8e72..e8672c2f3 100644 --- a/app/src/main/res/layout/episode_filter_dialog.xml +++ b/app/src/main/res/layout/episode_filter_dialog.xml @@ -40,4 +40,21 @@ android:minLines="1" android:scrollbars="vertical" /> + <CheckBox + android:id="@+id/checkbox_filter_duration" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/episode_filters_duration" /> + + <EditText + android:id="@+id/etxtEpisodeFilterDurationText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:cursorVisible="true" + android:focusable="true" + android:focusableInTouchMode="true" + android:inputType="numberSigned" + android:lines="1" /> + </LinearLayout> diff --git a/app/src/main/res/layout/feed_statistics.xml b/app/src/main/res/layout/feed_statistics.xml new file mode 100644 index 000000000..f8f5ac555 --- /dev/null +++ b/app/src/main/res/layout/feed_statistics.xml @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="utf-8"?> +<TableLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TableRow + android:tag="detailed"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/statistics_episodes_started_total" /> + + <TextView + android:id="@+id/startedTotalLabel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + tools:text="0 / 0" /> + + </TableRow> + + <TableRow> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/statistics_time_played" /> + + <TextView + android:id="@+id/timePlayedLabel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + tools:text="0 min" /> + + </TableRow> + + <TableRow + android:tag="detailed"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/statistics_duration_played_episodes" /> + + <TextView + android:id="@+id/durationPlayedLabel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + tools:text="0 min" /> + + </TableRow> + + <TableRow + android:tag="detailed"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/statistics_total_duration" /> + + <TextView + android:id="@+id/totalDurationLabel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + tools:text="0 min" /> + + </TableRow> + + <TableRow> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/statistics_episodes_on_device" /> + + <TextView + android:id="@+id/onDeviceLabel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + tools:text="0" /> + + </TableRow> + + <TableRow> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/statistics_space_used" /> + + <TextView + android:id="@+id/spaceUsedLabel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + tools:text="0 MB" /> + + </TableRow> + +</TableLayout> diff --git a/app/src/main/res/layout/feed_statistics_dialog.xml b/app/src/main/res/layout/feed_statistics_dialog.xml new file mode 100644 index 000000000..fcd36fe7a --- /dev/null +++ b/app/src/main/res/layout/feed_statistics_dialog.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.fragment.app.FragmentContainerView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/statisticsContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" /> diff --git a/app/src/main/res/layout/feedinfo.xml b/app/src/main/res/layout/feedinfo.xml index d753cbda1..b0a73cb97 100644 --- a/app/src/main/res/layout/feedinfo.xml +++ b/app/src/main/res/layout/feedinfo.xml @@ -1,210 +1,164 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout - 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:layout_width="match_parent" - android:layout_height="match_parent"> + 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:layout_width="match_parent" + android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout - android:id="@+id/appBar" - android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:id="@+id/appBar" + android:layout_width="match_parent" + android:layout_height="wrap_content"> <com.google.android.material.appbar.CollapsingToolbarLayout - android:id="@+id/collapsing_toolbar" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?android:attr/windowBackground" - app:contentScrim="?android:attr/windowBackground" - app:layout_scrollFlags="scroll|exitUntilCollapsed" - app:scrimAnimationDuration="200"> + android:id="@+id/collapsing_toolbar" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?android:attr/windowBackground" + app:contentScrim="?android:attr/windowBackground" + app:layout_scrollFlags="scroll|exitUntilCollapsed" + app:scrimAnimationDuration="200"> <ImageView - android:id="@+id/imgvBackground" - style="@style/BigBlurryBackground" - android:layout_width="match_parent" - android:layout_height="232dp" - android:background="@color/image_readability_tint" - app:layout_collapseMode="parallax" - app:layout_collapseParallaxMultiplier="0.6" /> + android:id="@+id/imgvBackground" + android:layout_width="match_parent" + android:layout_height="232dp" + android:background="@color/image_readability_tint" + style="@style/BigBlurryBackground" + app:layout_collapseMode="parallax" + app:layout_collapseParallaxMultiplier="0.6" /> <include - layout="@layout/feeditemlist_header" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="bottom" - app:layout_collapseMode="parallax" - app:layout_collapseParallaxMultiplier="0.6" /> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + layout="@layout/feeditemlist_header" + app:layout_collapseMode="parallax" + app:layout_collapseParallaxMultiplier="0.6" /> <androidx.appcompat.widget.Toolbar - android:id="@+id/toolbar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_alignParentTop="true" - android:minHeight="?attr/actionBarSize" - android:theme="?attr/actionBarTheme" - app:layout_collapseMode="pin" - app:navigationIcon="?homeAsUpIndicator" /> + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:minHeight="?attr/actionBarSize" + android:theme="?attr/actionBarTheme" + app:layout_collapseMode="pin" + app:navigationContentDescription="@string/toolbar_back_button_content_description" + app:navigationIcon="?homeAsUpIndicator" /> </com.google.android.material.appbar.CollapsingToolbarLayout> + </com.google.android.material.appbar.AppBarLayout> <androidx.core.widget.NestedScrollView - android:id="@+id/scrollView" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:clipToPadding="false" - android:paddingLeft="16dp" - android:paddingRight="16dp" - android:paddingBottom="8dp" - android:scrollbarStyle="outsideOverlay" - app:layout_behavior="@string/appbar_scrolling_view_behavior"> + android:id="@+id/scrollView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clipToPadding="false" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingBottom="8dp" + android:scrollbarStyle="outsideOverlay" + app:layout_behavior="@string/appbar_scrolling_view_behavior"> <LinearLayout - android:id="@+id/infoContainer" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:paddingHorizontal="@dimen/additional_horizontal_spacing"> + android:id="@+id/infoContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingHorizontal="@dimen/additional_horizontal_spacing"> <TextView - android:id="@+id/lblUrl" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="18sp" - android:layout_marginTop="16dp" - android:layout_marginBottom="4dp" - android:text="@string/url_label" - android:textColor="?android:attr/textColorPrimary" - tools:background="@android:color/holo_red_light" /> + android:id="@+id/lblUrl" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="18sp" + android:layout_marginTop="16dp" + android:layout_marginBottom="4dp" + android:text="@string/url_label" + android:textColor="?android:attr/textColorPrimary" + tools:background="@android:color/holo_red_light" /> <TextView - android:id="@+id/txtvUrl" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground" - android:maxLines="4" - android:paddingTop="4dp" - android:paddingBottom="4dp" - tools:background="@android:color/holo_green_dark" - tools:text="http://www.example.com/feed" /> + android:id="@+id/txtvUrl" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:maxLines="4" + android:paddingTop="4dp" + android:paddingBottom="4dp" + tools:background="@android:color/holo_green_dark" + tools:text="http://www.example.com/feed" /> <TextView - android:id="@+id/lblSupport" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:layout_marginBottom="4dp" - android:text="@string/support_funding_label" - android:textColor="?android:attr/textColorPrimary" - android:textSize="18sp" - tools:background="@android:color/holo_red_light" /> + android:id="@+id/lblSupport" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginBottom="4dp" + android:text="@string/support_funding_label" + android:textColor="?android:attr/textColorPrimary" + android:textSize="18sp" + tools:background="@android:color/holo_red_light" /> <TextView - android:id="@+id/txtvFundingUrl" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:maxLines="8" - android:paddingTop="4dp" - android:paddingBottom="4dp" - android:linksClickable="true" - android:autoLink="web" - tools:background="@android:color/holo_green_dark" /> + android:id="@+id/txtvFundingUrl" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:maxLines="8" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:linksClickable="true" + android:autoLink="web" + tools:background="@android:color/holo_green_dark" /> <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textSize="18sp" - android:layout_marginTop="16dp" - android:layout_marginBottom="4dp" - android:text="@string/description_label" - android:textColor="?android:attr/textColorPrimary" - tools:background="@android:color/holo_red_light" /> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="18sp" + android:layout_marginTop="16dp" + android:layout_marginBottom="4dp" + android:text="@string/description_label" + android:textColor="?android:attr/textColorPrimary" + tools:background="@android:color/holo_red_light" /> <TextView - android:id="@+id/txtvDescription" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/design_time_lorem_ipsum" - android:textIsSelectable="true" - tools:background="@android:color/holo_green_dark" /> + android:id="@+id/txtvDescription" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/design_time_lorem_ipsum" + android:textIsSelectable="true" + tools:background="@android:color/holo_green_dark" /> <TextView - android:id="@+id/lblStatistics" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="16dp" - android:textSize="18sp" - android:layout_marginBottom="8dp" - android:text="@string/statistics_label" - android:textColor="?android:attr/textColorPrimary" - tools:background="@android:color/holo_red_light" /> - - <TableLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <TableRow - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/statistics_listened_for" /> - - <TextView - android:id="@+id/txtvPodcastTime" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="5dp" /> - </TableRow> - - <TableRow - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/statistics_episodes_on_device" /> - - <TextView - android:id="@+id/txtvPodcastEpisodeCount" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="5dp" /> - </TableRow> - - <TableRow - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/statistics_space_used" /> - - <TextView - android:id="@+id/txtvPodcastSpaceUsed" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="5dp" /> - </TableRow> - - </TableLayout> + android:id="@+id/lblStatistics" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:textSize="18sp" + android:layout_marginBottom="8dp" + android:text="@string/statistics_label" + android:textColor="?android:attr/textColorPrimary" + tools:background="@android:color/holo_red_light" /> + + <androidx.fragment.app.FragmentContainerView + android:id="@+id/statisticsFragmentContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> <Button - android:id="@+id/btnvOpenStatistics" - style="@style/Widget.MaterialComponents.Button.TextButton" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:minWidth="0dp" - android:minHeight="0dp" - android:text="@string/statistics_view_all" /> + android:id="@+id/btnvOpenStatistics" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:minWidth="0dp" + android:minHeight="0dp" + android:text="@string/statistics_view_all" + style="@style/Widget.MaterialComponents.Button.TextButton" /> </LinearLayout> + </androidx.core.widget.NestedScrollView> </androidx.coordinatorlayout.widget.CoordinatorLayout> diff --git a/app/src/main/res/layout/feeditem_pager_fragment.xml b/app/src/main/res/layout/feeditem_pager_fragment.xml index ac7316dd8..690ac3bc1 100644 --- a/app/src/main/res/layout/feeditem_pager_fragment.xml +++ b/app/src/main/res/layout/feeditem_pager_fragment.xml @@ -11,6 +11,7 @@ android:layout_height="wrap_content" android:minHeight="?attr/actionBarSize" android:theme="?attr/actionBarTheme" + app:navigationContentDescription="@string/toolbar_back_button_content_description" app:navigationIcon="?homeAsUpIndicator" android:id="@+id/toolbar"/> diff --git a/app/src/main/res/layout/feedsettings.xml b/app/src/main/res/layout/feedsettings.xml index acd1089bd..df6e666eb 100644 --- a/app/src/main/res/layout/feedsettings.xml +++ b/app/src/main/res/layout/feedsettings.xml @@ -11,6 +11,7 @@ android:minHeight="?attr/actionBarSize" android:theme="?attr/actionBarTheme" app:title="@string/feed_settings_label" + app:navigationContentDescription="@string/toolbar_back_button_content_description" app:navigationIcon="?homeAsUpIndicator" android:elevation="4dp" android:id="@+id/toolbar"/> diff --git a/app/src/main/res/layout/fragment_subscriptions.xml b/app/src/main/res/layout/fragment_subscriptions.xml index 6dd112eed..5672a310f 100644 --- a/app/src/main/res/layout/fragment_subscriptions.xml +++ b/app/src/main/res/layout/fragment_subscriptions.xml @@ -2,6 +2,7 @@ <RelativeLayout 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:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> @@ -40,9 +41,11 @@ android:id="@+id/subscriptions_grid" android:layout_width="match_parent" android:layout_height="match_parent" + android:clipToPadding="false" android:layout_gravity="center_horizontal" android:paddingBottom="88dp" - android:clipToPadding="false" /> + tools:itemCount="2" + tools:listitem="@layout/subscription_item" /> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> diff --git a/app/src/main/res/layout/nextcloud_auth_dialog.xml b/app/src/main/res/layout/nextcloud_auth_dialog.xml new file mode 100644 index 000000000..345eec88b --- /dev/null +++ b/app/src/main/res/layout/nextcloud_auth_dialog.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="16dp" + android:orientation="vertical" + android:clipToPadding="false"> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/serverUrlTextInput" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/serverUrlText" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/gpodnetauth_host" + android:inputType="textNoSuggestions" + android:lines="1" + android:imeOptions="actionNext|flagNoFullscreen" /> + + </com.google.android.material.textfield.TextInputLayout> + + <LinearLayout + android:id="@+id/loginProgressContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + android:orientation="horizontal" + android:layout_gravity="center_vertical"> + + <ProgressBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/synchronization_nextcloud_authenticate_browser" /> + + </LinearLayout> + + <TextView + android:id="@+id/errorText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:textColor="@color/download_failed_red" + android:layout_marginBottom="16dp" /> + + <Button + android:id="@+id/loginButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/gpodnetauth_login_butLabel" /> + +</LinearLayout> diff --git a/app/src/main/res/layout/playback_speed_feed_setting_dialog.xml b/app/src/main/res/layout/playback_speed_feed_setting_dialog.xml new file mode 100644 index 000000000..572096911 --- /dev/null +++ b/app/src/main/res/layout/playback_speed_feed_setting_dialog.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <CheckBox + android:id="@+id/useGlobalCheckbox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/feed_auto_download_global" + android:layout_marginBottom="8dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical"> + + <de.danoeh.antennapod.view.PlaybackSpeedSeekBar + android:id="@+id/seekBar" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <TextView + android:id="@+id/currentSpeedLabel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" /> + + </LinearLayout> + +</LinearLayout> diff --git a/app/src/main/res/layout/playback_speed_seek_bar.xml b/app/src/main/res/layout/playback_speed_seek_bar.xml index 8c9b1725f..155a2261a 100644 --- a/app/src/main/res/layout/playback_speed_seek_bar.xml +++ b/app/src/main/res/layout/playback_speed_seek_bar.xml @@ -14,6 +14,7 @@ android:text="-" android:clickable="true" android:focusable="true" + android:scrollbars="none" android:textStyle="bold" android:textSize="24sp" android:textColor="?attr/colorSecondary" @@ -36,6 +37,7 @@ android:text="+" android:clickable="true" android:focusable="true" + android:scrollbars="none" android:textStyle="bold" android:textSize="24sp" android:textColor="?attr/colorSecondary" diff --git a/app/src/main/res/layout/quick_feed_discovery_item.xml b/app/src/main/res/layout/quick_feed_discovery_item.xml index cb03b6677..c3a32f019 100644 --- a/app/src/main/res/layout/quick_feed_discovery_item.xml +++ b/app/src/main/res/layout/quick_feed_discovery_item.xml @@ -2,6 +2,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:squareImageView="http://schemas.android.com/apk/de.danoeh.antennapod" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="4dp" @@ -14,7 +15,8 @@ android:elevation="4dp" android:outlineProvider="bounds" android:foreground="?android:attr/selectableItemBackground" - squareImageView:direction="width" /> + squareImageView:direction="width" + tools:src="@android:drawable/sym_def_app_icon"/> </LinearLayout> diff --git a/app/src/main/res/layout/subscription_selection_activity.xml b/app/src/main/res/layout/subscription_selection_activity.xml new file mode 100644 index 000000000..b54e7e4a4 --- /dev/null +++ b/app/src/main/res/layout/subscription_selection_activity.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/transparentBackground" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <androidx.cardview.widget.CardView + android:id="@+id/card" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="32dp" + android:elevation="16dp" + app:cardCornerRadius="4dp"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?attr/actionBarSize" + android:theme="?attr/actionBarTheme" + android:layout_alignParentTop="true" /> + + <View + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_below="@id/toolbar" + android:background="?android:attr/listDivider" /> + + <ListView + android:id="@+id/list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clipToPadding="false" + android:layout_below="@id/divider" + android:paddingBottom="88dp" /> + + <Button + android:id="@+id/shortcutBtn" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="16dp" + android:layout_alignParentBottom="true" + android:text="@string/add_shortcut" /> + + </RelativeLayout> + + </androidx.cardview.widget.CardView> + +</LinearLayout> diff --git a/app/src/main/res/layout/time_dialog.xml b/app/src/main/res/layout/time_dialog.xml index 6b6ab3195..138a60b33 100644 --- a/app/src/main/res/layout/time_dialog.xml +++ b/app/src/main/res/layout/time_dialog.xml @@ -1,136 +1,144 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - xmlns:tools="http://schemas.android.com/tools" - android:orientation="vertical" - android:gravity="center" - android:padding="16dp"> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center" + android:padding="16dp"> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/timeSetup" - android:orientation="vertical"> + android:id="@+id/timeSetup" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> <EditText - android:id="@+id/etxtTime" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_margin="8dp" - android:ems="2" - android:inputType="number" - android:maxLength="3"/> + android:id="@+id/etxtTime" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_margin="8dp" + android:ems="2" + android:inputType="number" + android:maxLength="3" /> <Spinner - android:id="@+id/spTimeUnit" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:layout_marginTop="8dp"/> + android:id="@+id/spTimeUnit" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:layout_marginTop="8dp" /> + </LinearLayout> <Button - android:text="@string/set_sleeptimer_label" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/setSleeptimerButton"/> + android:id="@+id/setSleeptimerButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/set_sleeptimer_label" /> + </LinearLayout> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/timeDisplay" - android:orientation="vertical" - android:visibility="gone"> + android:id="@+id/timeDisplay" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="visible"> <TextView - android:text="00:00:00" - android:layout_gravity="center" - android:gravity="center" - android:textSize="32sp" - android:textColor="?android:attr/textColorPrimary" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:id="@+id/time"/> + android:id="@+id/time" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="00:00:00" + android:layout_gravity="center" + android:gravity="center" + android:textSize="32sp" + android:textColor="?android:attr/textColorPrimary" /> <Button - android:text="@string/disable_sleeptimer_label" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:id="@+id/disableSleeptimerButton"/> + android:id="@+id/disableSleeptimerButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/disable_sleeptimer_label" /> <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_vertical"> <Button - android:id="@+id/extendSleepFiveMinutesButton" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginEnd="4dp" - android:layout_marginRight="4dp" - android:layout_weight="1" - android:padding="5dp" - tools:text="+5 min" /> + android:id="@+id/extendSleepFiveMinutesButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="4dp" + android:layout_marginRight="4dp" + android:paddingHorizontal="2dp" + android:paddingVertical="4dp" + android:layout_weight="1" + style="?attr/materialButtonOutlinedStyle" + tools:text="+5 min" /> <Button - android:id="@+id/extendSleepTenMinutesButton" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="4dp" - android:layout_marginEnd="4dp" - android:layout_weight="1" - tools:text="+10 min" /> + android:id="@+id/extendSleepTenMinutesButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:layout_marginEnd="4dp" + android:paddingHorizontal="2dp" + android:paddingVertical="4dp" + android:layout_weight="1" + style="?attr/materialButtonOutlinedStyle" + tools:text="+10 min" /> <Button - android:id="@+id/extendSleepTwentyMinutesButton" - style="?attr/materialButtonOutlinedStyle" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="4dp" - android:layout_marginRight="4dp" - android:layout_marginLeft="4dp" - android:layout_weight="1" - tools:text="+20 min" /> + android:id="@+id/extendSleepTwentyMinutesButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:layout_marginRight="4dp" + android:layout_marginLeft="4dp" + android:paddingHorizontal="2dp" + android:paddingVertical="4dp" + android:layout_weight="1" + style="?attr/materialButtonOutlinedStyle" + tools:text="+20 min" /> </LinearLayout> </LinearLayout> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:layout_marginTop="8dp"> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginTop="8dp"> <CheckBox - android:id="@+id/cbShakeToReset" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/shake_to_reset_label"/> + android:id="@+id/cbShakeToReset" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/shake_to_reset_label" /> <CheckBox - android:id="@+id/cbVibrate" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/timer_vibration_label"/> + android:id="@+id/cbVibrate" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/timer_vibration_label" /> <CheckBox - android:id="@+id/chAutoEnable" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/auto_enable_label"/> + android:id="@+id/chAutoEnable" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/auto_enable_label" /> </LinearLayout> diff --git a/app/src/main/res/menu/cast_enabled.xml b/app/src/main/res/menu/cast_enabled.xml deleted file mode 100644 index d6e85c311..000000000 --- a/app/src/main/res/menu/cast_enabled.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<menu xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:custom="http://schemas.android.com/apk/res-auto"> - - <item - android:id="@+id/media_route_menu_item" - android:title="@string/cast_media_route_menu_title" - custom:actionProviderClass="de.danoeh.antennapod.core.cast.SwitchableMediaRouteActionProvider" - custom:showAsAction="ifRoom"/> -</menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/nav_feed_action_speeddial.xml b/app/src/main/res/menu/nav_feed_action_speeddial.xml index 2dfa002bb..43bd25842 100644 --- a/app/src/main/res/menu/nav_feed_action_speeddial.xml +++ b/app/src/main/res/menu/nav_feed_action_speeddial.xml @@ -25,4 +25,9 @@ android:menuCategory="container" android:title="@string/playback_speed" android:icon="@drawable/ic_playback_speed"/> + <item + android:id="@+id/edit_tags" + android:menuCategory="container" + android:title="@string/add_to_folder" + android:icon="@drawable/ic_tag"/> </menu> diff --git a/app/src/main/res/menu/nav_feed_context.xml b/app/src/main/res/menu/nav_feed_context.xml index 17c15cbb0..e45fe24e0 100644 --- a/app/src/main/res/menu/nav_feed_context.xml +++ b/app/src/main/res/menu/nav_feed_context.xml @@ -7,7 +7,7 @@ android:title="@string/remove_all_new_flags_label" /> <item - android:id="@+id/add_to_folder" + android:id="@+id/edit_tags" android:menuCategory="container" android:title="@string/add_to_folder" /> diff --git a/app/src/main/res/xml/feed_settings.xml b/app/src/main/res/xml/feed_settings.xml index 457ff6e5b..007f084c9 100644 --- a/app/src/main/res/xml/feed_settings.xml +++ b/app/src/main/res/xml/feed_settings.xml @@ -1,72 +1,71 @@ <?xml version="1.0" encoding="utf-8"?> -<PreferenceScreen - xmlns:android="http://schemas.android.com/apk/res/android" +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="feedSettingsScreen"> <SwitchPreferenceCompat - android:key="keepUpdated" - android:icon="@drawable/ic_refresh" - android:title="@string/keep_updated" - android:summary="@string/keep_updated_summary"/> + android:icon="@drawable/ic_refresh" + android:key="keepUpdated" + android:summary="@string/keep_updated_summary" + android:title="@string/keep_updated" /> <SwitchPreferenceCompat - android:key="episodeNotification" - android:defaultValue="false" - android:dependency="keepUpdated" - android:icon="@drawable/ic_notifications" - android:title="@string/episode_notification" - android:summary="@string/episode_notification_summary"/> + android:defaultValue="false" + android:dependency="keepUpdated" + android:icon="@drawable/ic_notifications" + android:key="episodeNotification" + android:summary="@string/episode_notification_summary" + android:title="@string/episode_notification" /> <Preference - android:key="authentication" - android:icon="@drawable/ic_key" - android:title="@string/authentication_label" - android:summary="@string/authentication_descr"/> + android:icon="@drawable/ic_key" + android:key="authentication" + android:summary="@string/authentication_descr" + android:title="@string/authentication_label" /> <Preference - android:key="tags" - android:icon="@drawable/ic_folder" - android:title="@string/feed_folders_label" - android:summary="@string/feed_folders_summary"/> + android:icon="@drawable/ic_tag" + android:key="tags" + android:summary="@string/feed_tags_summary" + android:title="@string/feed_tags_label" /> - <ListPreference - android:key="feedPlaybackSpeed" - android:icon="@drawable/ic_playback_speed" - android:title="@string/playback_speed" - android:summary="@string/pref_feed_playback_speed_sum"/> + <Preference + android:icon="@drawable/ic_playback_speed" + android:key="feedPlaybackSpeed" + android:summary="@string/pref_feed_playback_speed_sum" + android:title="@string/playback_speed" /> <Preference - android:key="feedAutoSkip" - android:icon="@drawable/ic_skip_24dp" - android:summary="@string/pref_feed_skip_sum" - android:title="@string/pref_feed_skip" /> + android:icon="@drawable/ic_skip_24dp" + android:key="feedAutoSkip" + android:summary="@string/pref_feed_skip_sum" + android:title="@string/pref_feed_skip" /> <ListPreference - android:entries="@array/spnAutoDeleteItems" - android:entryValues="@array/spnAutoDeleteValues" - android:icon="@drawable/ic_delete" - android:title="@string/auto_delete_label" - android:summary="@string/feed_auto_download_global" - android:key="autoDelete"/> + android:entries="@array/spnAutoDeleteItems" + android:entryValues="@array/spnAutoDeleteValues" + android:icon="@drawable/ic_delete" + android:key="autoDelete" + android:summary="@string/feed_auto_download_global" + android:title="@string/auto_delete_label" /> <ListPreference - android:entries="@array/spnVolumeReductionItems" - android:entryValues="@array/spnVolumeReductionValues" - android:icon="@drawable/ic_volume_adaption" - android:summary="@string/feed_volume_reduction_summary" - android:title="@string/feed_volume_reduction" - android:defaultValue="off" - android:key="volumeReduction"/> + android:defaultValue="off" + android:entries="@array/spnVolumeReductionItems" + android:entryValues="@array/spnVolumeReductionValues" + android:icon="@drawable/ic_volume_adaption" + android:key="volumeReduction" + android:summary="@string/feed_volume_reduction_summary" + android:title="@string/feed_volume_reduction" /> <PreferenceCategory - android:title="@string/auto_download_settings_label" - android:key="autoDownloadCategory"> + android:key="autoDownloadCategory" + android:title="@string/auto_download_settings_label"> <SwitchPreferenceCompat - android:key="autoDownload" - android:title="@string/auto_download_label"/> + android:key="autoDownload" + android:title="@string/auto_download_label" /> <Preference - android:key="episodeFilter" - android:title="@string/episode_filters_label" - android:summary="@string/episode_filters_description"/> + android:key="episodeFilter" + android:summary="@string/episode_filters_description" + android:title="@string/episode_filters_label" /> </PreferenceCategory> </PreferenceScreen> diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 000000000..d4c3fc996 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config xmlns:tools="http://schemas.android.com/tools"> + <base-config cleartextTrafficPermitted="true" tools:ignore="InsecureBaseConfiguration"> + <trust-anchors> + <certificates src="user" tools:ignore="AcceptsUserCertificates"/> + <certificates src="system" /> + </trust-anchors> + </base-config> +</network-security-config> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index d528945c7..7c5012899 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -28,7 +28,7 @@ android:icon="@drawable/ic_network" /> <Preference - android:key="prefScreenGpodder" + android:key="prefScreenSynchronization" android:title="@string/synchronization_pref" android:summary="@string/synchronization_sum" android:icon="@drawable/ic_cloud" /> diff --git a/app/src/main/res/xml/preferences_gpodder.xml b/app/src/main/res/xml/preferences_gpodder.xml deleted file mode 100644 index a210b8e11..000000000 --- a/app/src/main/res/xml/preferences_gpodder.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<PreferenceScreen - xmlns:android="http://schemas.android.com/apk/res/android"> - <Preference - android:key="pref_gpodnet_description" - android:icon="@drawable/gpodder_icon" - android:summary="@string/gpodnet_description"/> - <Preference - android:key="pref_gpodnet_authenticate" - android:title="@string/pref_gpodnet_authenticate_title" - android:summary="@string/pref_gpodnet_authenticate_sum"/> - <Preference - android:key="pref_gpodnet_setlogin_information" - android:title="@string/pref_gpodnet_setlogin_information_title" - android:summary="@string/pref_gpodnet_setlogin_information_sum"/> - <Preference - android:key="pref_gpodnet_sync" - android:title="@string/pref_gpodnet_sync_changes_title" - android:summary="@string/pref_gpodnet_sync_changes_sum"/> - <Preference - android:key="pref_gpodnet_force_full_sync" - android:title="@string/pref_gpodnet_full_sync_title" - android:summary="@string/pref_gpodnet_full_sync_sum"/> - <Preference - android:key="pref_gpodnet_logout" - android:title="@string/pref_gpodnet_logout_title"/> - -</PreferenceScreen> diff --git a/app/src/main/res/xml/preferences_playback.xml b/app/src/main/res/xml/preferences_playback.xml index 2be8492eb..add9e8d4c 100644 --- a/app/src/main/res/xml/preferences_playback.xml +++ b/app/src/main/res/xml/preferences_playback.xml @@ -23,7 +23,7 @@ android:summary="@string/pref_unpauseOnBluetoothReconnect_sum" android:title="@string/pref_unpauseOnBluetoothReconnect_title"/> <SwitchPreferenceCompat - android:defaultValue="false" + android:defaultValue="true" android:enabled="true" android:key="prefPauseForFocusLoss" android:summary="@string/pref_pausePlaybackForFocusLoss_sum" @@ -127,11 +127,5 @@ android:title="@string/media_player" android:summary="@string/pref_media_player_message" android:entryValues="@array/media_player_values"/> - <SwitchPreferenceCompat - android:defaultValue="false" - android:enabled="true" - android:key="prefCast" - android:summary="@string/pref_cast_message" - android:title="@string/pref_cast_title"/> </PreferenceCategory> </PreferenceScreen> diff --git a/app/src/main/res/xml/preferences_synchronization.xml b/app/src/main/res/xml/preferences_synchronization.xml new file mode 100644 index 000000000..fbd4ccc79 --- /dev/null +++ b/app/src/main/res/xml/preferences_synchronization.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<PreferenceScreen + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <Preference + android:key="preference_synchronization_description" + android:icon="@drawable/ic_notification_sync" + android:summary="@string/synchronization_summary_unchoosen"/> + + <Preference + android:key="pref_gpodnet_setlogin_information" + android:title="@string/pref_gpodnet_setlogin_information_title" + android:summary="@string/pref_gpodnet_setlogin_information_sum" + app:isPreferenceVisible="false"/> + + <Preference + android:key="pref_synchronization_sync" + android:title="@string/synchronization_sync_changes_title" + android:summary="@string/synchronization_sync_summary"/> + + <Preference + android:key="pref_synchronization_force_full_sync" + android:title="@string/synchronization_full_sync_title" + android:summary="@string/synchronization_force_sync_summary"/> + + <Preference + android:key="pref_synchronization_logout" + android:title="@string/synchronization_logout"/> + +</PreferenceScreen> diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml index a16b679e3..045996714 100644 --- a/app/src/main/res/xml/provider_paths.xml +++ b/app/src/main/res/xml/provider_paths.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> <paths> <external-path name="external_storage" path="."/> - <root-path name="external_files" path="/storage/" /> + <files-path name="name" path="." /> </paths> diff --git a/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java deleted file mode 100644 index 753feb3e7..000000000 --- a/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java +++ /dev/null @@ -1,157 +0,0 @@ -package de.danoeh.antennapod.activity; - -import android.content.SharedPreferences; -import android.media.AudioManager; -import android.os.Bundle; -import androidx.preference.PreferenceManager; -import androidx.appcompat.app.AppCompatActivity; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; - -import com.google.android.gms.cast.ApplicationMetadata; - -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.CastManager; -import de.danoeh.antennapod.core.cast.DefaultCastConsumer; -import de.danoeh.antennapod.core.cast.SwitchableMediaRouteActionProvider; -import de.danoeh.antennapod.core.preferences.UserPreferences; -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 - * network. - */ -public abstract class CastEnabledActivity extends AppCompatActivity - implements SharedPreferences.OnSharedPreferenceChangeListener { - public static final String TAG = "CastEnabledActivity"; - - private CastConsumer castConsumer; - private CastManager castManager; - private final List<CastButtonVisibilityManager> castButtons = new ArrayList<>(); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (!CastManager.isInitialized()) { - return; - } - - PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) - .registerOnSharedPreferenceChangeListener(this); - - castConsumer = new DefaultCastConsumer() { - @Override - public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) { - onCastConnectionChanged(true); - } - - @Override - public void onDisconnected() { - onCastConnectionChanged(false); - } - }; - castManager = CastManager.getInstance(); - castManager.addCastConsumer(castConsumer); - CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager(castManager); - castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled()); - onCastConnectionChanged(castManager.isConnected()); - castButtons.add(castButtonVisibilityManager); - } - - @Override - protected void onDestroy() { - if (!CastManager.isInitialized()) { - super.onDestroy(); - return; - } - PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) - .unregisterOnSharedPreferenceChangeListener(this); - castManager.removeCastConsumer(castConsumer); - super.onDestroy(); - } - - @Override - protected void onResume() { - super.onResume(); - if (!CastManager.isInitialized()) { - return; - } - for (CastButtonVisibilityManager castButton : castButtons) { - castButton.setResumed(true); - } - } - - @Override - protected void onPause() { - super.onPause(); - if (!CastManager.isInitialized()) { - return; - } - for (CastButtonVisibilityManager castButton : castButtons) { - castButton.setResumed(false); - } - } - - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (UserPreferences.PREF_CAST_ENABLED.equals(key)) { - boolean newValue = UserPreferences.isCastEnabled(); - Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + 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. - if (!newValue && !PlaybackService.isRunning) { - CastManager.getInstance().disconnect(); - } - } - } - - private void onCastConnectionChanged(boolean connected) { - if (connected) { - for (CastButtonVisibilityManager castButton : castButtons) { - castButton.onConnected(); - } - setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE); - } else { - for (CastButtonVisibilityManager castButton : castButtons) { - castButton.onDisconnected(); - } - setVolumeControlStream(AudioManager.STREAM_MUSIC); - } - } - - /** - * Should be called by any activity or fragment for which the cast button should be shown. - */ - public final void requestCastButton(Menu menu) { - if (!CastManager.isInitialized()) { - return; - } - - MenuItem mediaRouteButton = menu.findItem(R.id.media_route_menu_item); - if (mediaRouteButton == null) { - getMenuInflater().inflate(R.menu.cast_enabled, menu); - mediaRouteButton = menu.findItem(R.id.media_route_menu_item); - } - - SwitchableMediaRouteActionProvider mediaRouteActionProvider = - CastManager.getInstance().addMediaRouterButton(mediaRouteButton); - CastButtonVisibilityManager castButtonVisibilityManager = - new CastButtonVisibilityManager(CastManager.getInstance()); - castButtonVisibilityManager.setMenu(menu); - castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled()); - castButtonVisibilityManager.mediaRouteActionProvider = mediaRouteActionProvider; - castButtonVisibilityManager.setResumed(true); - castButtonVisibilityManager.requestCastButton(MenuItem.SHOW_AS_ACTION_ALWAYS); - mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable()); - } -} diff --git a/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java b/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java deleted file mode 100644 index 2a879c62d..000000000 --- a/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.danoeh.antennapod.config; - -import androidx.annotation.NonNull; -import androidx.mediarouter.app.MediaRouteControllerDialogFragment; -import androidx.mediarouter.app.MediaRouteDialogFactory; - -import de.danoeh.antennapod.core.CastCallbacks; -import de.danoeh.antennapod.fragment.CustomMRControllerDialogFragment; - -public class CastCallbackImpl implements CastCallbacks { - @Override - public MediaRouteDialogFactory getMediaRouterDialogFactory() { - return new MediaRouteDialogFactory() { - @NonNull - @Override - public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() { - return new CustomMRControllerDialogFragment(); - } - }; - } -} diff --git a/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java b/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java deleted file mode 100644 index 6d8450a18..000000000 --- a/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java +++ /dev/null @@ -1,480 +0,0 @@ -package de.danoeh.antennapod.dialog; - -import android.app.PendingIntent; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.os.RemoteException; -import androidx.annotation.NonNull; -import android.support.v4.media.MediaDescriptionCompat; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaControllerCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; -import androidx.core.util.Pair; -import androidx.core.view.MarginLayoutParamsCompat; -import androidx.core.view.accessibility.AccessibilityEventCompat; -import androidx.mediarouter.app.MediaRouteControllerDialog; -import androidx.palette.graphics.Palette; -import androidx.mediarouter.media.MediaRouter; -import androidx.appcompat.widget.AppCompatImageView; -import android.text.TextUtils; -import android.util.Log; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewGroup; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; -import android.widget.FrameLayout; -import android.widget.ImageButton; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.bumptech.glide.request.target.Target; - -import java.util.concurrent.ExecutionException; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.glide.ApGlideSettings; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; - -public class CustomMRControllerDialog extends MediaRouteControllerDialog { - public static final String TAG = "CustomMRContrDialog"; - - private MediaRouter mediaRouter; - private MediaSessionCompat.Token token; - - private ImageView artView; - private TextView titleView; - private TextView subtitleView; - private ImageButton playPauseButton; - private LinearLayout rootView; - - private boolean viewsCreated = false; - - private Disposable fetchArtSubscription; - - private MediaControllerCompat mediaController; - private MediaControllerCompat.Callback mediaControllerCallback; - - public CustomMRControllerDialog(Context context) { - this(context, 0); - } - - private CustomMRControllerDialog(Context context, int theme) { - super(context, theme); - mediaRouter = MediaRouter.getInstance(getContext()); - token = mediaRouter.getMediaSessionToken(); - try { - if (token != null) { - mediaController = new MediaControllerCompat(getContext(), token); - } - } catch (RemoteException e) { - Log.e(TAG, "Error creating media controller", e); - } - - if (mediaController != null) { - mediaControllerCallback = new MediaControllerCompat.Callback() { - @Override - public void onSessionDestroyed() { - if (mediaController != null) { - mediaController.unregisterCallback(mediaControllerCallback); - mediaController = null; - } - } - - @Override - public void onMetadataChanged(MediaMetadataCompat metadata) { - updateViews(); - } - - @Override - public void onPlaybackStateChanged(PlaybackStateCompat state) { - updateState(); - } - }; - mediaController.registerCallback(mediaControllerCallback); - } - } - - @Override - public View onCreateMediaControlView(Bundle savedInstanceState) { - boolean landscape = getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; - if (landscape) { - /* - * When a horizontal LinearLayout measures itself, it first measures its children and - * settles their widths on the first pass, and only then figures out its height, never - * revisiting the widths measurements. - * When one has a child view that imposes a certain aspect ratio (such as an ImageView), - * then its width and height are related to each other, and so if one allows for a large - * height, then it will request for itself a large width as well. However, on the first - * child measurement, the LinearLayout imposes a very relaxed height bound, that the - * child uses to tell the width it wants, a value which the LinearLayout will interpret - * as final, even though the child will want to change it once a more restrictive height - * bound is imposed later. - * - * Our solution is, given that the heights of the children do not depend on their widths - * in this case, we first figure out the layout's height and only then perform the - * usual sequence of measurements. - * - * Note: this solution does not take into account any vertical paddings nor children's - * vertical margins in determining the height, as this View as well as its children are - * defined in code and no paddings/margins that would influence these computations are - * introduced. - * - * There were no resources online for this type of issue as far as I could gather. - */ - rootView = new LinearLayout(getContext()) { - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // We'd like to find the overall height before adjusting the widths within the LinearLayout - int maxHeight = Integer.MIN_VALUE; - if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) { - for (int i = 0; i < getChildCount(); i++) { - int height = Integer.MIN_VALUE; - View child = getChildAt(i); - ViewGroup.LayoutParams lp = child.getLayoutParams(); - // we only measure children whose layout_height is not MATCH_PARENT - if (lp.height >= 0) { - height = lp.height; - } else if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { - child.measure(widthMeasureSpec, heightMeasureSpec); - height = child.getMeasuredHeight(); - } - maxHeight = Math.max(maxHeight, height); - } - } - if (maxHeight > 0) { - super.onMeasure(widthMeasureSpec, - MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY)); - } else { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - } - }; - rootView.setOrientation(LinearLayout.HORIZONTAL); - } else { - rootView = new LinearLayout(getContext()); - rootView.setOrientation(LinearLayout.VERTICAL); - } - FrameLayout.LayoutParams rootParams = new FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - rootParams.setMargins(0, 0, 0, - getContext().getResources().getDimensionPixelSize(R.dimen.media_router_controller_bottom_margin)); - rootView.setLayoutParams(rootParams); - - // Start the session activity when a content item (album art, title or subtitle) is clicked. - View.OnClickListener onClickListener = v -> { - if (mediaController != null) { - PendingIntent pi = mediaController.getSessionActivity(); - if (pi != null) { - try { - pi.send(); - dismiss(); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, pi + " was not sent, it had been canceled."); - } - } - } - }; - - LinearLayout.LayoutParams artParams; - /* - * On portrait orientation, we want to limit the artView's height to 9/16 of the available - * width. Reason is that we need to choose the height wisely otherwise we risk the dialog - * being much larger than the screen, and there doesn't seem to be a good way to know the - * available height beforehand. - * - * On landscape orientation, we want to limit the artView's width to its available height. - * Otherwise, horizontal images would take too much space and severely restrict the space - * for episode title and play/pause button. - * - * Internal implementation of ImageView only uses the source image's aspect ratio, but we - * want to impose our own and fallback to the source image's when it is more favorable. - * Solutions were inspired, among other similar sources, on - * http://stackoverflow.com/questions/18077325/scale-image-to-fill-imageview-width-and-keep-aspect-ratio - */ - if (landscape) { - artView = new AppCompatImageView(getContext()) { - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int desiredWidth = widthMeasureSpec; - int desiredMeasureMode = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ? - MeasureSpec.EXACTLY : MeasureSpec.AT_MOST; - if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { - Drawable drawable = getDrawable(); - if (drawable != null) { - int intrHeight = drawable.getIntrinsicHeight(); - int intrWidth = drawable.getIntrinsicWidth(); - int originalHeight = MeasureSpec.getSize(heightMeasureSpec); - if (intrHeight < intrWidth) { - desiredWidth = MeasureSpec.makeMeasureSpec( - originalHeight, desiredMeasureMode); - } else { - desiredWidth = MeasureSpec.makeMeasureSpec( - Math.round((float) originalHeight * intrWidth / intrHeight), - desiredMeasureMode); - } - } - } - super.onMeasure(desiredWidth, heightMeasureSpec); - } - }; - artParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.MATCH_PARENT); - MarginLayoutParamsCompat.setMarginStart(artParams, - getContext().getResources().getDimensionPixelSize(R.dimen.media_router_controller_playback_control_horizontal_spacing)); - } else { - artView = new AppCompatImageView(getContext()) { - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int desiredHeight = heightMeasureSpec; - if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) { - Drawable drawable = getDrawable(); - if (drawable != null) { - int originalWidth = MeasureSpec.getSize(widthMeasureSpec); - int intrHeight = drawable.getIntrinsicHeight(); - int intrWidth = drawable.getIntrinsicWidth(); - float scale; - if (intrHeight*16 > intrWidth*9) { - // image is taller than 16:9 - scale = (float) originalWidth * 9 / 16 / intrHeight; - } else { - // image is more horizontal than 16:9 - scale = (float) originalWidth / intrWidth; - } - desiredHeight = MeasureSpec.makeMeasureSpec( - Math.round(intrHeight * scale), - MeasureSpec.EXACTLY); - } - } - super.onMeasure(widthMeasureSpec, desiredHeight); - } - }; - artParams = new LinearLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - } - // When we fetch the bitmap, we want to know if we should set a background color or not. - artView.setTag(landscape); - - artView.setScaleType(ImageView.ScaleType.FIT_CENTER); - artView.setOnClickListener(onClickListener); - - artView.setLayoutParams(artParams); - rootView.addView(artView); - - ViewGroup wrapper = rootView; - - if (landscape) { - // Here we wrap with a frame layout because we want to set different layout parameters - // for landscape orientation. - wrapper = new FrameLayout(getContext()); - wrapper.setLayoutParams(new LinearLayout.LayoutParams( - 0, - ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); - rootView.addView(wrapper); - rootView.setWeightSum(1f); - } - - View playbackControlLayout = View.inflate(getContext(), R.layout.media_router_controller, wrapper); - - titleView = playbackControlLayout.findViewById(R.id.mrc_control_title); - subtitleView = playbackControlLayout.findViewById(R.id.mrc_control_subtitle); - playbackControlLayout.findViewById(R.id.mrc_control_title_container).setOnClickListener(onClickListener); - playPauseButton = playbackControlLayout.findViewById(R.id.mrc_control_play_pause); - playPauseButton.setOnClickListener(v -> { - PlaybackStateCompat state; - if (mediaController != null && (state = mediaController.getPlaybackState()) != null) { - boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING; - if (isPlaying) { - mediaController.getTransportControls().pause(); - } else { - mediaController.getTransportControls().play(); - } - // Announce the action for accessibility. - AccessibilityManager accessibilityManager = (AccessibilityManager) - getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - if (accessibilityManager != null && accessibilityManager.isEnabled()) { - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEventCompat.TYPE_ANNOUNCEMENT); - event.setPackageName(getContext().getPackageName()); - event.setClassName(getClass().getName()); - int resId = isPlaying ? R.string.mr_controller_pause : R.string.mr_controller_play; - event.getText().add(getContext().getString(resId)); - accessibilityManager.sendAccessibilityEvent(event); - } - } - }); - - viewsCreated = true; - updateViews(); - return rootView; - } - - @Override - public void onDetachedFromWindow() { - if (fetchArtSubscription != null) { - fetchArtSubscription.dispose(); - fetchArtSubscription = null; - } - super.onDetachedFromWindow(); - } - - private void updateViews() { - if (!viewsCreated || token == null || mediaController == null) { - rootView.setVisibility(View.GONE); - return; - } - MediaMetadataCompat metadata = mediaController.getMetadata(); - MediaDescriptionCompat description = metadata == null ? null : metadata.getDescription(); - if (description == null) { - rootView.setVisibility(View.GONE); - return; - } - - PlaybackStateCompat state = mediaController.getPlaybackState(); - MediaRouter.RouteInfo route = MediaRouter.getInstance(getContext()).getSelectedRoute(); - - CharSequence title = description.getTitle(); - boolean hasTitle = !TextUtils.isEmpty(title); - CharSequence subtitle = description.getSubtitle(); - boolean hasSubtitle = !TextUtils.isEmpty(subtitle); - - boolean showTitle = false; - boolean showSubtitle = false; - if (route.getPresentationDisplay() != null && - route.getPresentationDisplay().getDisplayId() != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) { - // The user is currently casting screen. - titleView.setText(R.string.mr_controller_casting_screen); - showTitle = true; - } else if (state == null || state.getState() == PlaybackStateCompat.STATE_NONE) { - // Show "No media selected" as we don't yet know the playback state. - // (Only exception is bluetooth where we don't show anything.) - if (!route.isBluetooth()) { - titleView.setText(R.string.mr_controller_no_media_selected); - showTitle = true; - } - } else if (!hasTitle && !hasSubtitle) { - titleView.setText(R.string.mr_controller_no_info_available); - showTitle = true; - } else { - if (hasTitle) { - titleView.setText(title); - showTitle = true; - } - if (hasSubtitle) { - subtitleView.setText(subtitle); - showSubtitle = true; - } - } - if (showSubtitle) { - titleView.setSingleLine(); - } else { - titleView.setMaxLines(2); - } - titleView.setVisibility(showTitle ? View.VISIBLE : View.GONE); - subtitleView.setVisibility(showSubtitle ? View.VISIBLE : View.GONE); - - updateState(); - - if(rootView.getVisibility() != View.VISIBLE) { - artView.setVisibility(View.GONE); - rootView.setVisibility(View.VISIBLE); - } - - if (fetchArtSubscription != null) { - fetchArtSubscription.dispose(); - } - - fetchArtSubscription = Observable.fromCallable(() -> fetchArt(description)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(result -> { - fetchArtSubscription = null; - if (artView == null) { - return; - } - if (result.first != null) { - if (!((Boolean) artView.getTag())) { - artView.setBackgroundColor(result.second); - } - artView.setImageBitmap(result.first); - artView.setVisibility(View.VISIBLE); - } else { - artView.setVisibility(View.GONE); - } - }, error -> Log.e(TAG, Log.getStackTraceString(error))); - - } - - private void updateState() { - PlaybackStateCompat state; - if (!viewsCreated || mediaController == null || - (state = mediaController.getPlaybackState()) == null) { - return; - } - boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_BUFFERING - || state.getState() == PlaybackStateCompat.STATE_PLAYING; - boolean supportsPlay = (state.getActions() & (PlaybackStateCompat.ACTION_PLAY - | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0; - boolean supportsPause = (state.getActions() & (PlaybackStateCompat.ACTION_PAUSE - | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0; - if (isPlaying && supportsPause) { - playPauseButton.setVisibility(View.VISIBLE); - playPauseButton.setImageResource(getThemeResource(getContext(), R.attr.mediaRoutePauseDrawable)); - playPauseButton.setContentDescription(getContext().getResources().getText(R.string.mr_controller_pause)); - } else if (!isPlaying && supportsPlay) { - playPauseButton.setVisibility(View.VISIBLE); - playPauseButton.setImageResource(getThemeResource(getContext(), R.attr.mediaRoutePlayDrawable)); - playPauseButton.setContentDescription(getContext().getResources().getText(R.string.mr_controller_play)); - } else { - playPauseButton.setVisibility(View.GONE); - } - } - - private static int getThemeResource(Context context, int attr) { - TypedValue value = new TypedValue(); - return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0; - } - - @NonNull - private Pair<Bitmap, Integer> fetchArt(@NonNull MediaDescriptionCompat description) { - Bitmap iconBitmap = description.getIconBitmap(); - Uri iconUri = description.getIconUri(); - Bitmap art = null; - if (iconBitmap != null) { - art = iconBitmap; - } else if (iconUri != null) { - try { - art = Glide.with(getContext().getApplicationContext()) - .asBitmap() - .load(iconUri.toString()) - .apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY)) - .submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) - .get(); - } catch (InterruptedException | ExecutionException e) { - Log.e(TAG, "Image art load failed", e); - } - } - int backgroundColor = 0; - if (art != null && art.getWidth()*9 < art.getHeight()*16) { - // Portrait art requires dominant color as background color. - Palette palette = new Palette.Builder(art).maximumColorCount(1).generate(); - backgroundColor = palette.getSwatches().isEmpty() - ? 0 : palette.getSwatches().get(0).getRgb(); - } - return new Pair<>(art, backgroundColor); - } -} diff --git a/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java b/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java deleted file mode 100644 index dad7b0bfd..000000000 --- a/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java +++ /dev/null @@ -1,15 +0,0 @@ -package de.danoeh.antennapod.fragment; - -import android.content.Context; -import android.os.Bundle; -import androidx.mediarouter.app.MediaRouteControllerDialog; -import androidx.mediarouter.app.MediaRouteControllerDialogFragment; - -import de.danoeh.antennapod.dialog.CustomMRControllerDialog; - -public class CustomMRControllerDialogFragment extends MediaRouteControllerDialogFragment { - @Override - public MediaRouteControllerDialog onCreateControllerDialog(Context context, Bundle savedInstanceState) { - return new CustomMRControllerDialog(context); - } -} diff --git a/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java b/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java deleted file mode 100644 index b51fb40b0..000000000 --- a/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java +++ /dev/null @@ -1,48 +0,0 @@ -package de.danoeh.antennapod.preferences; - -import android.content.Context; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GoogleApiAvailability; - -import de.danoeh.antennapod.PodcastApp; -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.fragment.preferences.PlaybackPreferencesFragment; - -/** - * Implements functions from PreferenceController that are flavor dependent. - */ -public class PreferenceControllerFlavorHelper { - - public static void setupFlavoredUI(PlaybackPreferencesFragment ui) { - //checks whether Google Play Services is installed on the device (condition necessary for Cast support) - ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setOnPreferenceChangeListener((preference, o) -> { - if (o instanceof Boolean && ((Boolean) o)) { - final int googlePlayServicesCheck = GoogleApiAvailability.getInstance() - .isGooglePlayServicesAvailable(ui.getActivity()); - if (googlePlayServicesCheck == ConnectionResult.SUCCESS) { - displayRestartRequiredDialog(ui.requireContext()); - return true; - } else { - GoogleApiAvailability.getInstance() - .getErrorDialog(ui.getActivity(), googlePlayServicesCheck, 0) - .show(); - return false; - } - } - return true; - }); - } - - private static void displayRestartRequiredDialog(@NonNull Context context) { - AlertDialog.Builder dialog = new AlertDialog.Builder(context); - dialog.setTitle(android.R.string.dialog_alert_title); - dialog.setMessage(R.string.pref_restart_required); - dialog.setPositiveButton(android.R.string.ok, (dialog1, which) -> PodcastApp.forceRestart()); - dialog.setCancelable(false); - dialog.show(); - } -} diff --git a/app/src/play/res/layout/media_router_controller.xml b/app/src/play/res/layout/media_router_controller.xml deleted file mode 100644 index bdb1b1cc2..000000000 --- a/app/src/play/res/layout/media_router_controller.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/mrc_playback_control" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="@dimen/media_router_controller_playback_control_vertical_padding" - android:paddingBottom="@dimen/media_router_controller_playback_control_vertical_padding" - android:paddingLeft="@dimen/media_router_controller_playback_control_start_padding" - android:paddingStart="@dimen/media_router_controller_playback_control_start_padding" - android:paddingRight="@dimen/media_router_controller_playback_control_horizontal_spacing" - android:paddingEnd="@dimen/media_router_controller_playback_control_horizontal_spacing"> - <ImageButton android:id="@+id/mrc_control_play_pause" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/media_router_controller_playback_control_horizontal_spacing" - android:layout_marginStart="@dimen/media_router_controller_playback_control_horizontal_spacing" - android:layout_alignParentRight="true" - android:layout_alignParentEnd="true" - android:contentDescription="@string/mr_controller_play" - android:background="?android:attr/selectableItemBackground"/> - - <LinearLayout android:id="@+id/mrc_control_title_container" - android:orientation="vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_toLeftOf="@id/mrc_control_play_pause" - android:layout_toStartOf="@id/mrc_control_play_pause" - android:layout_centerVertical="true"> - <TextView android:id="@+id/mrc_control_title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.MediaRouter.PrimaryText"/> - <TextView android:id="@+id/mrc_control_subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textAppearance="@style/TextAppearance.MediaRouter.SecondaryText" - android:singleLine="true" /> - </LinearLayout> -</RelativeLayout> |