summaryrefslogtreecommitdiff
path: root/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'app/src')
-rw-r--r--app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java37
-rw-r--r--app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java5
-rw-r--r--app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java56
-rw-r--r--app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java130
-rw-r--r--app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java43
-rw-r--r--app/src/androidTest/java/de/test/antennapod/handler/AtomParserTest.java40
-rw-r--r--app/src/androidTest/java/de/test/antennapod/handler/FeedParserTestBase.java154
-rw-r--r--app/src/androidTest/java/de/test/antennapod/handler/RssParserTest.java63
-rw-r--r--app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java5
-rw-r--r--app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java2
-rw-r--r--app/src/androidTest/java/de/test/antennapod/service/playback/PlaybackServiceTaskManagerTest.java20
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java40
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java217
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java119
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java53
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java411
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java289
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java79
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java857
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java51
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java2
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java14
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java3
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java6
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java1
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java104
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java157
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java248
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java124
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/syndication/feedgenerator/AtomGenerator.java131
-rw-r--r--app/src/main/AndroidManifest.xml53
-rw-r--r--app/src/main/assets/licenses.xml8
-rw-r--r--app/src/main/assets/special_thanks.csv1
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java88
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java112
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java65
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java99
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java26
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java16
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java10
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java53
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java395
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java21
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java10
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java5
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java1
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java5
-rw-r--r--app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java20
-rw-r--r--app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java7
-rw-r--r--app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java31
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java35
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java19
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java59
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java12
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java22
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java8
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java52
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java6
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java99
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java6
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java1
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java36
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java8
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java17
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java12
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java14
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java25
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java1
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java1
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java17
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java302
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java17
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java29
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java12
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/UserInterfacePreferencesFragment.java16
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java1
-rw-r--r--app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java12
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java92
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java124
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedIndicatorView.java113
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/RecursiveRadioGroup.java67
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java62
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java35
-rw-r--r--app/src/main/java/de/danoeh/antennapod/view/viewholder/EpisodeItemViewHolder.java37
-rw-r--r--app/src/main/res/layout/activity_widget_config.xml29
-rw-r--r--app/src/main/res/layout/addfeed.xml18
-rw-r--r--app/src/main/res/layout/audioplayer_fragment.xml56
-rw-r--r--app/src/main/res/layout/authentication_dialog.xml81
-rw-r--r--app/src/main/res/layout/bug_report.xml6
-rw-r--r--app/src/main/res/layout/cover_fragment.xml2
-rw-r--r--app/src/main/res/layout/download_authentication_activity.xml64
-rw-r--r--app/src/main/res/layout/edit_text_dialog.xml2
-rw-r--r--app/src/main/res/layout/feeditem_fragment.xml9
-rw-r--r--app/src/main/res/layout/filter_dialog_row.xml4
-rw-r--r--app/src/main/res/layout/gpodnetauth_activity.xml10
-rw-r--r--app/src/main/res/layout/gpodnetauth_credentials.xml169
-rw-r--r--app/src/main/res/layout/gpodnetauth_device.xml125
-rw-r--r--app/src/main/res/layout/gpodnetauth_device_row.xml13
-rw-r--r--app/src/main/res/layout/gpodnetauth_dialog.xml20
-rw-r--r--app/src/main/res/layout/gpodnetauth_finish.xml32
-rw-r--r--app/src/main/res/layout/gpodnetauth_host.xml50
-rw-r--r--app/src/main/res/layout/onlinefeedview_activity.xml72
-rw-r--r--app/src/main/res/layout/quick_feed_discovery.xml5
-rw-r--r--app/src/main/res/layout/quick_feed_discovery_item.xml2
-rw-r--r--app/src/main/res/layout/searchlist_item_feed.xml2
-rw-r--r--app/src/main/res/layout/secondary_action.xml6
-rw-r--r--app/src/main/res/layout/subscription_item.xml2
-rw-r--r--app/src/main/res/layout/time_dialog.xml47
-rw-r--r--app/src/main/res/layout/videoplayer_activity.xml28
-rw-r--r--app/src/main/res/menu/bug_report_options.xml7
-rw-r--r--app/src/main/res/menu/feedinfo.xml26
-rw-r--r--app/src/main/res/menu/subscriptions.xml31
-rw-r--r--app/src/main/res/xml/actions.xml25
-rw-r--r--app/src/main/res/xml/feed_settings.xml8
-rw-r--r--app/src/main/res/xml/player_widget_info.xml3
-rw-r--r--app/src/main/res/xml/preferences.xml4
-rw-r--r--app/src/main/res/xml/preferences_about.xml2
-rw-r--r--app/src/main/res/xml/preferences_gpodder.xml14
-rw-r--r--app/src/main/res/xml/preferences_notifications.xml41
-rw-r--r--app/src/main/res/xml/preferences_playback.xml29
-rw-r--r--app/src/main/res/xml/preferences_user_interface.xml12
-rw-r--r--app/src/play/java/de/danoeh/antennapod/dialog/RatingDialog.java1
147 files changed, 1736 insertions, 5241 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java
index 3c8c5d7f0..21498effd 100644
--- a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java
+++ b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java
@@ -3,8 +3,10 @@ package de.test.antennapod;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
+import androidx.test.espresso.NoMatchingViewException;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.espresso.PerformException;
import androidx.test.espresso.UiController;
@@ -15,6 +17,9 @@ import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.espresso.util.HumanReadables;
import androidx.test.espresso.util.TreeIterables;
import android.view.View;
+
+import junit.framework.AssertionFailedError;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -33,6 +38,7 @@ import java.util.concurrent.TimeoutException;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
@@ -57,7 +63,7 @@ public class EspressoTestUtils {
@Override
public String getDescription() {
- return "wait for a specific view for" + millis + " millis.";
+ return "wait for a specific view for " + millis + " millis.";
}
@Override
@@ -88,6 +94,33 @@ public class EspressoTestUtils {
}
/**
+ * Wait until a certain view becomes visible, but at the longest until the timeout.
+ * Unlike {@link #waitForView(Matcher, long)} it doesn't stick to the initial root view.
+ *
+ * @param viewMatcher The view to wait for.
+ * @param timeoutMillis Maximum waiting period in milliseconds.
+ * @throws Exception Throws an Exception in case of a timeout.
+ */
+ public static void waitForViewGlobally(@NonNull Matcher<View> viewMatcher, long timeoutMillis) throws Exception {
+ long startTime = System.currentTimeMillis();
+ long endTime = startTime + timeoutMillis;
+
+ do {
+ try {
+ onView(viewMatcher).check(matches(isDisplayed()));
+ // no Exception thrown -> check successful
+ return;
+ } catch (NoMatchingViewException | AssertionFailedError ignore) {
+ // check was not successful "not found" -> continue waiting
+ }
+ //noinspection BusyWait
+ Thread.sleep(50);
+ } while (System.currentTimeMillis() < endTime);
+
+ throw new Exception("Timeout after " + timeoutMillis + " ms");
+ }
+
+ /**
* Perform action of waiting for a specific view id.
* https://stackoverflow.com/a/30338665/
* @param id The id of the child to click.
@@ -113,7 +146,7 @@ public class EspressoTestUtils {
}
/**
- * Clear all app databases
+ * Clear all app databases.
*/
public static void clearPreferences() {
File root = InstrumentationRegistry.getInstrumentation().getTargetContext().getFilesDir().getParentFile();
diff --git a/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java b/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java
index 8c628efd5..e31838671 100644
--- a/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/dialogs/ShareDialogTest.java
@@ -11,15 +11,11 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.test.antennapod.EspressoTestUtils;
import de.test.antennapod.ui.UITestUtils;
@@ -70,7 +66,6 @@ public class ShareDialogTest {
onView(withText(R.string.all_episodes_short_label)).perform(click());
Matcher<View> allEpisodesMatcher;
- final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
allEpisodesMatcher = Matchers.allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2));
onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, click()));
diff --git a/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java b/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java
deleted file mode 100644
index 83d7a4d22..000000000
--- a/app/src/androidTest/java/de/test/antennapod/entities/ExternalMediaTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package de.test.antennapod.entities;
-
-import android.annotation.SuppressLint;
-import android.content.SharedPreferences;
-import androidx.preference.PreferenceManager;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.MediaType;
-import de.danoeh.antennapod.core.util.playback.ExternalMedia;
-import org.junit.After;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Tests for {@link ExternalMedia} entity.
- */
-@SmallTest
-public class ExternalMediaTest {
-
- private static final int NOT_SET = -1;
-
- @After
- public void tearDown() throws Exception {
- clearSharedPrefs();
- }
-
- @SuppressLint("CommitPrefEdits")
- private void clearSharedPrefs() {
- SharedPreferences prefs = getDefaultSharedPrefs();
- SharedPreferences.Editor editor = prefs.edit();
- editor.clear();
- editor.commit();
- }
-
- private SharedPreferences getDefaultSharedPrefs() {
- return PreferenceManager.getDefaultSharedPreferences(InstrumentationRegistry.getInstrumentation().getTargetContext());
- }
-
- @Test
- public void testSaveCurrentPositionUpdatesPreferences() {
- final int POSITION = 50;
- final int LAST_PLAYED_TIME = 1650;
-
- assertEquals(NOT_SET, getDefaultSharedPrefs().getInt(ExternalMedia.PREF_POSITION, NOT_SET));
- assertEquals(NOT_SET, getDefaultSharedPrefs().getLong(ExternalMedia.PREF_LAST_PLAYED_TIME, NOT_SET));
-
- ExternalMedia media = new ExternalMedia("source", MediaType.AUDIO);
- media.saveCurrentPosition(getDefaultSharedPrefs(), POSITION, LAST_PLAYED_TIME);
-
- assertEquals(POSITION, getDefaultSharedPrefs().getInt(ExternalMedia.PREF_POSITION, NOT_SET));
- assertEquals(LAST_PLAYED_TIME, getDefaultSharedPrefs().getLong(ExternalMedia.PREF_LAST_PLAYED_TIME, NOT_SET));
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java b/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java
deleted file mode 100644
index fc2943205..000000000
--- a/app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package de.test.antennapod.feed;
-
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.FeedFilter;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-@SmallTest
-public class FeedFilterTest {
-
- @Test
- public void testNullFilter() throws Exception {
- FeedFilter filter = new FeedFilter();
- FeedItem item = new FeedItem();
- item.setTitle("Hello world");
-
- assertFalse(filter.excludeOnly());
- assertFalse(filter.includeOnly());
- assertEquals("", filter.getExcludeFilter());
- assertEquals("", filter.getIncludeFilter());
- assertTrue(filter.shouldAutoDownload(item));
- }
-
- @Test
- public void testBasicIncludeFilter() throws Exception {
- String includeFilter = "Hello";
- FeedFilter filter = new FeedFilter(includeFilter, "");
- FeedItem item = new FeedItem();
- item.setTitle("Hello world");
-
- FeedItem item2 = new FeedItem();
- item2.setTitle("Don't include me");
-
- assertFalse(filter.excludeOnly());
- assertTrue(filter.includeOnly());
- assertEquals("", filter.getExcludeFilter());
- assertEquals(includeFilter, filter.getIncludeFilter());
- assertTrue(filter.shouldAutoDownload(item));
- assertFalse(filter.shouldAutoDownload(item2));
- }
-
- @Test
- public void testBasicExcludeFilter() throws Exception {
- String excludeFilter = "Hello";
- FeedFilter filter = new FeedFilter("", excludeFilter);
- FeedItem item = new FeedItem();
- item.setTitle("Hello world");
-
- FeedItem item2 = new FeedItem();
- item2.setTitle("Item2");
-
- assertTrue(filter.excludeOnly());
- assertFalse(filter.includeOnly());
- assertEquals(excludeFilter, filter.getExcludeFilter());
- assertEquals("", filter.getIncludeFilter());
- assertFalse(filter.shouldAutoDownload(item));
- assertTrue(filter.shouldAutoDownload(item2));
- }
-
- @Test
- public void testComplexIncludeFilter() throws Exception {
- String includeFilter = "Hello \n\"Two words\"";
- FeedFilter filter = new FeedFilter(includeFilter, "");
- FeedItem item = new FeedItem();
- item.setTitle("hello world");
-
- FeedItem item2 = new FeedItem();
- item2.setTitle("Two three words");
-
- FeedItem item3 = new FeedItem();
- item3.setTitle("One two words");
-
- assertFalse(filter.excludeOnly());
- assertTrue(filter.includeOnly());
- assertEquals("", filter.getExcludeFilter());
- assertEquals(includeFilter, filter.getIncludeFilter());
- assertTrue(filter.shouldAutoDownload(item));
- assertFalse(filter.shouldAutoDownload(item2));
- assertTrue(filter.shouldAutoDownload(item3));
- }
-
- @Test
- public void testComplexExcludeFilter() throws Exception {
- String excludeFilter = "Hello \"Two words\"";
- FeedFilter filter = new FeedFilter("", excludeFilter);
- FeedItem item = new FeedItem();
- item.setTitle("hello world");
-
- FeedItem item2 = new FeedItem();
- item2.setTitle("One three words");
-
- FeedItem item3 = new FeedItem();
- item3.setTitle("One two words");
-
- assertTrue(filter.excludeOnly());
- assertFalse(filter.includeOnly());
- assertEquals(excludeFilter, filter.getExcludeFilter());
- assertEquals("", filter.getIncludeFilter());
- assertFalse(filter.shouldAutoDownload(item));
- assertTrue(filter.shouldAutoDownload(item2));
- assertFalse(filter.shouldAutoDownload(item3));
- }
-
- @Test
- public void testComboFilter() throws Exception {
- String includeFilter = "Hello world";
- String excludeFilter = "dislike";
- FeedFilter filter = new FeedFilter(includeFilter, excludeFilter);
-
- FeedItem download = new FeedItem();
- download.setTitle("Hello everyone!");
- // because, while it has words from the include filter it also has exclude words
- FeedItem doNotDownload = new FeedItem();
- doNotDownload.setTitle("I dislike the world");
- // because it has no words from the include filter
- FeedItem doNotDownload2 = new FeedItem();
- doNotDownload2.setTitle("no words to include");
-
- assertTrue(filter.hasExcludeFilter());
- assertTrue(filter.hasIncludeFilter());
- assertTrue(filter.shouldAutoDownload(download));
- assertFalse(filter.shouldAutoDownload(doNotDownload));
- assertFalse(filter.shouldAutoDownload(doNotDownload2));
- }
-
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java b/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java
deleted file mode 100644
index 0b9a67d0a..000000000
--- a/app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package de.test.antennapod.feed;
-
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-@SmallTest
-public class FeedItemTest {
- private static final String TEXT_LONG = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
- private static final String TEXT_SHORT = "Lorem ipsum";
-
- /**
- * If one of `description` or `content:encoded` is null, use the other one.
- */
- @Test
- public void testShownotesNullValues() throws Exception {
- testShownotes(null, TEXT_LONG);
- testShownotes(TEXT_LONG, null);
- }
-
- /**
- * If `description` is reasonably longer than `content:encoded`, use `description`.
- */
- @Test
- public void testShownotesLength() throws Exception {
- testShownotes(TEXT_SHORT, TEXT_LONG);
- testShownotes(TEXT_LONG, TEXT_SHORT);
- }
-
- /**
- * Checks if the shownotes equal TEXT_LONG, using the given `description` and `content:encoded`
- * @param description Description of the feed item
- * @param contentEncoded `content:encoded` of the feed item
- */
- private void testShownotes(String description, String contentEncoded) throws Exception {
- FeedItem item = new FeedItem();
- item.setDescription(description);
- item.setContentEncoded(contentEncoded);
- assertEquals(TEXT_LONG, item.loadShownotes().call());
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/handler/AtomParserTest.java b/app/src/androidTest/java/de/test/antennapod/handler/AtomParserTest.java
deleted file mode 100644
index de9f53ae2..000000000
--- a/app/src/androidTest/java/de/test/antennapod/handler/AtomParserTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package de.test.antennapod.handler;
-
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.test.antennapod.util.syndication.feedgenerator.AtomGenerator;
-import org.junit.Test;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Tests for Atom feeds in FeedHandler.
- */
-@SmallTest
-public class AtomParserTest extends FeedParserTestBase {
- @Test
- public void testAtomBasic() throws Exception {
- Feed f1 = createTestFeed(10, true);
- Feed f2 = runFeedTest(f1, new AtomGenerator(), "UTF-8", 0);
- feedValid(f1, f2, Feed.TYPE_ATOM1);
- }
-
- @Test
- public void testLogoWithWhitespace() throws Exception {
- String logo = "https://example.com/image.png";
- Feed f1 = createTestFeed(0, false);
- f1.setImageUrl(null);
- Feed f2 = runFeedTest(f1, new AtomGenerator() {
- @Override
- protected void writeAdditionalAttributes(XmlSerializer xml) throws IOException {
- xml.startTag(null, "logo");
- xml.text(" " + logo + "\n");
- xml.endTag(null, "logo");
- }
- }, "UTF-8", 0);
- assertEquals(logo, f2.getImageUrl());
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/handler/FeedParserTestBase.java b/app/src/androidTest/java/de/test/antennapod/handler/FeedParserTestBase.java
deleted file mode 100644
index 83f334633..000000000
--- a/app/src/androidTest/java/de/test/antennapod/handler/FeedParserTestBase.java
+++ /dev/null
@@ -1,154 +0,0 @@
-package de.test.antennapod.handler;
-
-import android.content.Context;
-import androidx.test.platform.app.InstrumentationRegistry;
-import de.danoeh.antennapod.core.feed.Chapter;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
-import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeException;
-import de.test.antennapod.util.syndication.feedgenerator.FeedGenerator;
-import org.junit.After;
-import org.junit.Before;
-import org.xml.sax.SAXException;
-
-import javax.xml.parsers.ParserConfigurationException;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Tests for FeedHandler.
- */
-public abstract class FeedParserTestBase {
- private static final String FEEDS_DIR = "testfeeds";
-
- private File file = null;
- private OutputStream outputStream = null;
-
- @Before
- public void setUp() throws Exception {
- Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- File destDir = context.getExternalFilesDir(FEEDS_DIR);
- assertNotNull(destDir);
-
- file = new File(destDir, "feed.xml");
- file.delete();
-
- assertNotNull(file);
- assertFalse(file.exists());
-
- outputStream = new FileOutputStream(file);
- }
-
-
- @After
- public void tearDown() throws Exception {
- file.delete();
- file = null;
-
- outputStream.close();
- outputStream = null;
- }
-
- protected Feed runFeedTest(Feed feed, FeedGenerator g, String encoding, long flags)
- throws IOException, UnsupportedFeedtypeException, SAXException, ParserConfigurationException {
- g.writeFeed(feed, outputStream, encoding, flags);
- FeedHandler handler = new FeedHandler();
- Feed parsedFeed = new Feed(feed.getDownload_url(), feed.getLastUpdate());
- parsedFeed.setFile_url(file.getAbsolutePath());
- parsedFeed.setDownloaded(true);
- handler.parseFeed(parsedFeed);
- return parsedFeed;
- }
-
- protected void feedValid(Feed feed, Feed parsedFeed, String feedType) {
- assertEquals(feed.getTitle(), parsedFeed.getTitle());
- if (feedType.equals(Feed.TYPE_ATOM1)) {
- assertEquals(feed.getFeedIdentifier(), parsedFeed.getFeedIdentifier());
- } else {
- assertEquals(feed.getLanguage(), parsedFeed.getLanguage());
- }
-
- assertEquals(feed.getLink(), parsedFeed.getLink());
- assertEquals(feed.getDescription(), parsedFeed.getDescription());
- assertEquals(feed.getPaymentLink(), parsedFeed.getPaymentLink());
- assertEquals(feed.getImageUrl(), parsedFeed.getImageUrl());
-
- if (feed.getItems() != null) {
- assertNotNull(parsedFeed.getItems());
- assertEquals(feed.getItems().size(), parsedFeed.getItems().size());
-
- for (int i = 0; i < feed.getItems().size(); i++) {
- FeedItem item = feed.getItems().get(i);
- FeedItem parsedItem = parsedFeed.getItems().get(i);
-
- if (item.getItemIdentifier() != null) {
- assertEquals(item.getItemIdentifier(), parsedItem.getItemIdentifier());
- }
- assertEquals(item.getTitle(), parsedItem.getTitle());
- assertEquals(item.getDescription(), parsedItem.getDescription());
- assertEquals(item.getContentEncoded(), parsedItem.getContentEncoded());
- assertEquals(item.getLink(), parsedItem.getLink());
- assertEquals(item.getPubDate().getTime(), parsedItem.getPubDate().getTime());
- assertEquals(item.getPaymentLink(), parsedItem.getPaymentLink());
-
- if (item.hasMedia()) {
- assertTrue(parsedItem.hasMedia());
- FeedMedia media = item.getMedia();
- FeedMedia parsedMedia = parsedItem.getMedia();
-
- assertEquals(media.getDownload_url(), parsedMedia.getDownload_url());
- assertEquals(media.getSize(), parsedMedia.getSize());
- assertEquals(media.getMime_type(), parsedMedia.getMime_type());
- }
-
- assertEquals(feed.getImageUrl(), item.getImageLocation());
-
- if (item.getChapters() != null) {
- assertNotNull(parsedItem.getChapters());
- assertEquals(item.getChapters().size(), parsedItem.getChapters().size());
- List<Chapter> chapters = item.getChapters();
- List<Chapter> parsedChapters = parsedItem.getChapters();
- for (int j = 0; j < chapters.size(); j++) {
- Chapter chapter = chapters.get(j);
- Chapter parsedChapter = parsedChapters.get(j);
-
- assertEquals(chapter.getTitle(), parsedChapter.getTitle());
- assertEquals(chapter.getLink(), parsedChapter.getLink());
- }
- }
- }
- }
- }
-
- protected Feed createTestFeed(int numItems, boolean withFeedMedia) {
- Feed feed = new Feed(0, null, "title", "http://example.com", "This is the description",
- "http://example.com/payment", "Daniel", "en", null, "http://example.com/feed",
- "http://example.com/picture", file.getAbsolutePath(), "http://example.com/feed", true);
- feed.setItems(new ArrayList<>());
-
- for (int i = 0; i < numItems; i++) {
- FeedItem item = new FeedItem(0, "item-" + i, "http://example.com/item-" + i,
- "http://example.com/items/" + i, new Date(i * 60000), FeedItem.UNPLAYED, feed);
- feed.getItems().add(item);
- if (withFeedMedia) {
- item.setMedia(new FeedMedia(0, item, 4711, 0, 1024 * 1024, "audio/mp3", null,
- "http://example.com/media-" + i, false, null, 0, 0));
- }
- }
-
- return feed;
- }
-
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/handler/RssParserTest.java b/app/src/androidTest/java/de/test/antennapod/handler/RssParserTest.java
deleted file mode 100644
index c2e319233..000000000
--- a/app/src/androidTest/java/de/test/antennapod/handler/RssParserTest.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package de.test.antennapod.handler;
-
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.MediaType;
-import de.danoeh.antennapod.core.syndication.namespace.NSMedia;
-import de.test.antennapod.util.syndication.feedgenerator.Rss2Generator;
-import org.junit.Test;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-
-import static org.junit.Assert.assertEquals;
-
-/**
- * Tests for RSS feeds in FeedHandler.
- */
-@SmallTest
-public class RssParserTest extends FeedParserTestBase {
- @Test
- public void testRss2Basic() throws Exception {
- Feed f1 = createTestFeed(10, true);
- Feed f2 = runFeedTest(f1, new Rss2Generator(), "UTF-8", Rss2Generator.FEATURE_WRITE_GUID);
- feedValid(f1, f2, Feed.TYPE_RSS2);
- }
-
- @Test
- public void testImageWithWhitespace() throws Exception {
- String image = "https://example.com/image.png";
- Feed f1 = createTestFeed(0, false);
- f1.setImageUrl(null);
- Feed f2 = runFeedTest(f1, new Rss2Generator() {
- @Override
- protected void writeAdditionalAttributes(XmlSerializer xml) throws IOException {
- xml.startTag(null, "image");
- xml.startTag(null, "url");
- xml.text(" " + image + "\n");
- xml.endTag(null, "url");
- xml.endTag(null, "image");
- }
- }, "UTF-8", 0);
- assertEquals(image, f2.getImageUrl());
- }
-
- @Test
- public void testMediaContentMime() throws Exception {
- Feed f1 = createTestFeed(0, false);
- f1.setImageUrl(null);
- Feed f2 = runFeedTest(f1, new Rss2Generator() {
- @Override
- protected void writeAdditionalAttributes(XmlSerializer xml) throws IOException {
- xml.setPrefix(NSMedia.NSTAG, NSMedia.NSURI);
- xml.startTag(null, "item");
- xml.startTag(NSMedia.NSURI, "content");
- xml.attribute(null, "url", "https://www.example.com/file.mp4");
- xml.attribute(null, "medium", "video");
- xml.endTag(NSMedia.NSURI, "content");
- xml.endTag(null, "item");
- }
- }, "UTF-8", 0);
- assertEquals(MediaType.VIDEO, f2.getItems().get(0).getMedia().getMediaType());
- }
-}
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 419cf2096..f3bd61839 100644
--- a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java
@@ -10,6 +10,7 @@ import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
+import de.danoeh.antennapod.core.feed.FeedItemFilter;
import org.awaitility.Awaitility;
import org.hamcrest.Matcher;
import org.junit.After;
@@ -252,7 +253,7 @@ public class PlaybackTest {
onView(isRoot()).perform(waitForView(withText(R.string.all_episodes_short_label), 1000));
onView(withText(R.string.all_episodes_short_label)).perform(click());
- final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
+ final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered());
Matcher<View> allEpisodesMatcher = allOf(withId(android.R.id.list), isDisplayed(), hasMinimumChildCount(2));
onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, clickChildViewWithId(R.id.secondaryActionButton)));
@@ -287,7 +288,7 @@ public class PlaybackTest {
uiTestUtils.addLocalFeedData(true);
DBWriter.clearQueue().get();
activityTestRule.launchActivity(new Intent());
- final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10);
+ final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10, FeedItemFilter.unfiltered());
startLocalPlayback();
FeedMedia media = episodes.get(0).getMedia();
diff --git a/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java b/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java
index fd395f7c1..70cf4166b 100644
--- a/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/service/download/DownloadServiceTest.java
@@ -17,10 +17,8 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.feed.Feed;
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 f039c8bdf..ddd4fe899 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
@@ -6,6 +6,7 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.filters.LargeTest;
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.junit.After;
@@ -187,8 +188,8 @@ public class PlaybackServiceTaskManagerTest {
}
@Override
- public void onWidgetUpdaterTick() {
-
+ public WidgetUpdater.WidgetState requestWidgetState() {
+ return null;
}
@Override
@@ -248,8 +249,9 @@ public class PlaybackServiceTaskManagerTest {
}
@Override
- public void onWidgetUpdaterTick() {
+ public WidgetUpdater.WidgetState requestWidgetState() {
countDownLatch.countDown();
+ return null;
}
@Override
@@ -348,8 +350,8 @@ public class PlaybackServiceTaskManagerTest {
}
@Override
- public void onWidgetUpdaterTick() {
-
+ public WidgetUpdater.WidgetState requestWidgetState() {
+ return null;
}
@Override
@@ -391,8 +393,8 @@ public class PlaybackServiceTaskManagerTest {
}
@Override
- public void onWidgetUpdaterTick() {
-
+ public WidgetUpdater.WidgetState requestWidgetState() {
+ return null;
}
@Override
@@ -449,8 +451,8 @@ public class PlaybackServiceTaskManagerTest {
}
@Override
- public void onWidgetUpdaterTick() {
-
+ public WidgetUpdater.WidgetState requestWidgetState() {
+ return null;
}
@Override
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java b/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java
index 5396b218d..e74cf49b7 100644
--- a/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java
@@ -3,15 +3,13 @@ package de.test.antennapod.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
-import de.danoeh.antennapod.core.ClientConfig;
-import de.danoeh.antennapod.core.DBTasksCallbacks;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.test.antennapod.EspressoTestUtils;
import de.test.antennapod.ui.UITestUtils;
@@ -31,8 +29,7 @@ public class AutoDownloadTest {
private Context context;
private UITestUtils stubFeedsServer;
-
- private DBTasksCallbacks dbTasksCallbacksOrig;
+ private StubDownloadAlgorithm stubDownloadAlgorithm;
@Before
public void setUp() throws Exception {
@@ -41,16 +38,19 @@ public class AutoDownloadTest {
stubFeedsServer = new UITestUtils(context);
stubFeedsServer.setup();
- dbTasksCallbacksOrig = ClientConfig.dbTasksCallbacks;
-
EspressoTestUtils.clearPreferences();
EspressoTestUtils.clearDatabase();
UserPreferences.setAllowMobileStreaming(true);
+
+ // Setup: enable automatic download
+ // it is not needed, as the actual automatic download is stubbed.
+ stubDownloadAlgorithm = new StubDownloadAlgorithm();
+ DBTasks.setDownloadAlgorithm(stubDownloadAlgorithm);
}
@After
public void tearDown() throws Exception {
- ClientConfig.dbTasksCallbacks = dbTasksCallbacksOrig;
+ DBTasks.setDownloadAlgorithm(new AutomaticDownloadAlgorithm());
EspressoTestUtils.tryKillPlaybackService();
stubFeedsServer.tearDown();
}
@@ -76,11 +76,6 @@ public class AutoDownloadTest {
FeedItem item0 = queue.get(0);
FeedItem item1 = queue.get(1);
- // Setup: enable automatic download
- // it is not needed, as the actual automatic download is stubbed.
- StubDownloadAlgorithm stubDownloadAlgorithm = new StubDownloadAlgorithm();
- useDownloadAlgorithm(stubDownloadAlgorithm);
-
// Actual test
// Play the first one in the queue
playEpisode(item0);
@@ -94,11 +89,10 @@ public class AutoDownloadTest {
} catch (ConditionTimeoutException cte) {
long actual = stubDownloadAlgorithm.getCurrentlyPlayingAtDownload();
fail("when auto download is triggered, the next episode should be playing: ("
- + item1.getId() + ", " + item1.getTitle() + ") . "
+ + item1.getId() + ", " + item1.getTitle() + ") . "
+ "Actual playing: (" + actual + ")"
);
}
-
}
private void playEpisode(@NonNull FeedItem item) {
@@ -113,21 +107,7 @@ public class AutoDownloadTest {
.until(() -> item.getMedia().getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
}
- private void useDownloadAlgorithm(final AutomaticDownloadAlgorithm downloadAlgorithm) {
- ClientConfig.dbTasksCallbacks = new DBTasksCallbacks() {
- @Override
- public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() {
- return downloadAlgorithm;
- }
-
- @Override
- public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() {
- return dbTasksCallbacksOrig.getEpisodeCacheCleanupAlgorithm();
- }
- };
- }
-
- private static class StubDownloadAlgorithm implements AutomaticDownloadAlgorithm {
+ private static class StubDownloadAlgorithm extends AutomaticDownloadAlgorithm {
private long currentlyPlaying = -1;
@Override
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java
deleted file mode 100644
index 6c36da13e..000000000
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java
+++ /dev/null
@@ -1,217 +0,0 @@
-package de.test.antennapod.storage;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import androidx.preference.PreferenceManager;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import static de.test.antennapod.storage.DBTestUtils.saveFeedlist;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class for DBTasks
- */
-@SmallTest
-public class DBCleanupTests {
- static final int EPISODE_CACHE_SIZE = 5;
- private int cleanupAlgorithm;
-
- Context context;
-
- private File destFolder;
-
- public DBCleanupTests() {
- setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_DEFAULT);
- }
-
- protected void setCleanupAlgorithm(int cleanupAlgorithm) {
- this.cleanupAlgorithm = cleanupAlgorithm;
- }
-
- @After
- public void tearDown() throws Exception {
- assertTrue(PodDBAdapter.deleteDatabase());
-
- cleanupDestFolder(destFolder);
- assertTrue(destFolder.delete());
- }
-
- private void cleanupDestFolder(File destFolder) {
- for (File f : destFolder.listFiles()) {
- assertTrue(f.delete());
- }
- }
-
- @Before
- public void setUp() throws Exception {
- context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- destFolder = new File(context.getCacheDir(), "DDCleanupTests");
- destFolder.mkdir();
- cleanupDestFolder(destFolder);
- assertNotNull(destFolder);
- assertTrue(destFolder.exists());
- assertTrue(destFolder.canWrite());
-
- // create new database
- PodDBAdapter.init(context);
- PodDBAdapter.deleteDatabase();
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.close();
-
- SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).edit();
- prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, Integer.toString(EPISODE_CACHE_SIZE));
- prefEdit.putString(UserPreferences.PREF_EPISODE_CLEANUP, Integer.toString(cleanupAlgorithm));
- prefEdit.putBoolean(UserPreferences.PREF_ENABLE_AUTODL, true);
- prefEdit.commit();
-
- UserPreferences.init(context);
- }
-
- @Test
- public void testPerformAutoCleanupShouldDelete() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- List<File> files = new ArrayList<>();
- populateItems(NUM_ITEMS, feed, items, files, FeedItem.PLAYED, false, false);
-
- DBTasks.performAutoCleanup(context);
- for (int i = 0; i < files.size(); i++) {
- if (i < EPISODE_CACHE_SIZE) {
- assertTrue(files.get(i).exists());
- } else {
- assertFalse(files.get(i).exists());
- }
- }
- }
-
- void populateItems(final int numItems, Feed feed, List<FeedItem> items,
- List<File> files, int itemState, boolean addToQueue,
- boolean addToFavorites) throws IOException {
- for (int i = 0; i < numItems; i++) {
- Date itemDate = new Date(numItems - i);
- Date playbackCompletionDate = null;
- if (itemState == FeedItem.PLAYED) {
- playbackCompletionDate = itemDate;
- }
- FeedItem item = new FeedItem(0, "title", "id", "link", itemDate, itemState, feed);
-
- File f = new File(destFolder, "file " + i);
- assertTrue(f.createNewFile());
- files.add(f);
- item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, playbackCompletionDate, 0, 0));
- items.add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- if (addToQueue) {
- adapter.setQueue(items);
- }
- if (addToFavorites) {
- adapter.setFavorites(items);
- }
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : items) {
- assertTrue(item.getId() != 0);
- assertTrue(item.getMedia().getId() != 0);
- }
- }
-
- @Test
- public void testPerformAutoCleanupHandleUnplayed() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- List<File> files = new ArrayList<>();
- populateItems(NUM_ITEMS, feed, items, files, FeedItem.UNPLAYED, false, false);
-
- DBTasks.performAutoCleanup(context);
- for (File file : files) {
- assertTrue(file.exists());
- }
- }
-
- @Test
- public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- List<File> files = new ArrayList<>();
- populateItems(NUM_ITEMS, feed, items, files, FeedItem.PLAYED, true, false);
-
- DBTasks.performAutoCleanup(context);
- for (File file : files) {
- assertTrue(file.exists());
- }
- }
-
- /**
- * Reproduces a bug where DBTasks.performAutoCleanup(android.content.Context) would use the ID of the FeedItem in the
- * call to DBWriter.deleteFeedMediaOfItem instead of the ID of the FeedMedia. This would cause the wrong item to be deleted.
- * @throws IOException
- */
- @Test
- public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue_withFeedsWithNoMedia() throws IOException {
- // add feed with no enclosures so that item ID != media ID
- saveFeedlist(1, 10, false);
-
- // add candidate for performAutoCleanup
- List<Feed> feeds = saveFeedlist(1, 1, true);
- FeedMedia m = feeds.get(0).getItems().get(0).getMedia();
- m.setDownloaded(true);
- m.setFile_url("file");
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setMedia(m);
- adapter.close();
-
- testPerformAutoCleanupShouldNotDeleteBecauseInQueue();
- }
-
- @Test
- public void testPerformAutoCleanupShouldNotDeleteBecauseFavorite() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- List<File> files = new ArrayList<>();
- populateItems(NUM_ITEMS, feed, items, files, FeedItem.PLAYED, false, true);
-
- DBTasks.performAutoCleanup(context);
- for (File file : files) {
- assertTrue(file.exists());
- }
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java
deleted file mode 100644
index d7ebf2351..000000000
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java
+++ /dev/null
@@ -1,119 +0,0 @@
-package de.test.antennapod.storage;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import androidx.preference.PreferenceManager;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Tests that the APNullCleanupAlgorithm is working correctly.
- */
-@SmallTest
-public class DBNullCleanupAlgorithmTest {
-
- private static final String TAG = "DBNullCleanupAlgorithmTest";
- private static final int EPISODE_CACHE_SIZE = 5;
-
- private Context context;
-
- private File destFolder;
-
- @After
- public void tearDown() throws Exception {
- assertTrue(PodDBAdapter.deleteDatabase());
-
- cleanupDestFolder(destFolder);
- assertTrue(destFolder.delete());
- }
-
- private void cleanupDestFolder(File destFolder) {
- for (File f : destFolder.listFiles()) {
- assertTrue(f.delete());
- }
- }
-
- @Before
- public void setUp() throws Exception {
- context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- destFolder = context.getExternalCacheDir();
- cleanupDestFolder(destFolder);
- assertNotNull(destFolder);
- assertTrue(destFolder.exists());
- assertTrue(destFolder.canWrite());
-
- // create new database
- PodDBAdapter.init(context);
- PodDBAdapter.deleteDatabase();
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.close();
-
- SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).edit();
- prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, Integer.toString(EPISODE_CACHE_SIZE));
- prefEdit.putString(UserPreferences.PREF_EPISODE_CLEANUP, Integer.toString(UserPreferences.EPISODE_CLEANUP_NULL));
- prefEdit.commit();
-
- UserPreferences.init(context);
- }
-
- /**
- * A test with no items in the queue, but multiple items downloaded.
- * The null algorithm should never delete any items, even if they're played and not in the queue.
- * @throws IOException
- */
- @Test
- public void testPerformAutoCleanupShouldNotDelete() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- List<File> files = new ArrayList<>();
- for (int i = 0; i < NUM_ITEMS; i++) {
- FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
-
- File f = new File(destFolder, "file " + i);
- assertTrue(f.createNewFile());
- files.add(f);
- item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true,
- new Date(NUM_ITEMS - i), 0, 0));
- items.add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : items) {
- assertTrue(item.getId() != 0);
- assertTrue(item.getMedia().getId() != 0);
- }
- DBTasks.performAutoCleanup(context);
- for (int i = 0; i < files.size(); i++) {
- assertTrue(files.get(i).exists());
- }
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java
deleted file mode 100644
index de810c701..000000000
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package de.test.antennapod.storage;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import org.junit.Test;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Tests that the APQueueCleanupAlgorithm is working correctly.
- */
-@SmallTest
-public class DBQueueCleanupAlgorithmTest extends DBCleanupTests {
-
- private static final String TAG = "DBQueueCleanupAlgorithmTest";
-
- public DBQueueCleanupAlgorithmTest() {
- setCleanupAlgorithm(UserPreferences.EPISODE_CLEANUP_QUEUE);
- }
-
- /**
- * For APQueueCleanupAlgorithm we expect even unplayed episodes to be deleted if needed
- * if they aren't in the queue
- */
- @Test
- public void testPerformAutoCleanupHandleUnplayed() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- List<File> files = new ArrayList<>();
- populateItems(NUM_ITEMS, feed, items, files, FeedItem.UNPLAYED, false, false);
-
- DBTasks.performAutoCleanup(context);
- for (int i = 0; i < files.size(); i++) {
- if (i < EPISODE_CACHE_SIZE) {
- assertTrue(files.get(i).exists());
- } else {
- assertFalse(files.get(i).exists());
- }
- }
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java
deleted file mode 100644
index 8811d9b31..000000000
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBReaderTest.java
+++ /dev/null
@@ -1,411 +0,0 @@
-package de.test.antennapod.storage;
-
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Random;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-import de.danoeh.antennapod.core.util.LongList;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import static de.test.antennapod.storage.DBTestUtils.saveFeedlist;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class for DBReader
- */
-@SmallTest
-public class DBReaderTest {
-
- @After
- public void tearDown() throws Exception {
- assertTrue(PodDBAdapter.deleteDatabase());
- }
-
- @Before
- public void setUp() throws Exception {
- // create new database
- PodDBAdapter.init(InstrumentationRegistry.getInstrumentation().getTargetContext());
- PodDBAdapter.deleteDatabase();
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.close();
- }
-
- @Test
- public void testGetFeedList() {
- List<Feed> feeds = saveFeedlist(10, 0, false);
- List<Feed> savedFeeds = DBReader.getFeedList();
- assertNotNull(savedFeeds);
- assertEquals(feeds.size(), savedFeeds.size());
- for (int i = 0; i < feeds.size(); i++) {
- assertEquals(feeds.get(i).getId(), savedFeeds.get(i).getId());
- }
- }
-
- @Test
- public void testGetFeedListSortOrder() {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
-
- Feed feed1 = new Feed(0, null, "A", "link", "d", null, null, null, "rss", "A", null, "", "", true);
- Feed feed2 = new Feed(0, null, "b", "link", "d", null, null, null, "rss", "b", null, "", "", true);
- Feed feed3 = new Feed(0, null, "C", "link", "d", null, null, null, "rss", "C", null, "", "", true);
- Feed feed4 = new Feed(0, null, "d", "link", "d", null, null, null, "rss", "d", null, "", "", true);
- adapter.setCompleteFeed(feed1);
- adapter.setCompleteFeed(feed2);
- adapter.setCompleteFeed(feed3);
- adapter.setCompleteFeed(feed4);
- assertTrue(feed1.getId() != 0);
- assertTrue(feed2.getId() != 0);
- assertTrue(feed3.getId() != 0);
- assertTrue(feed4.getId() != 0);
-
- adapter.close();
-
- List<Feed> saved = DBReader.getFeedList();
- assertNotNull(saved);
- assertEquals("Wrong size: ", 4, saved.size());
-
- assertEquals("Wrong id of feed 1: ", feed1.getId(), saved.get(0).getId());
- assertEquals("Wrong id of feed 2: ", feed2.getId(), saved.get(1).getId());
- assertEquals("Wrong id of feed 3: ", feed3.getId(), saved.get(2).getId());
- assertEquals("Wrong id of feed 4: ", feed4.getId(), saved.get(3).getId());
- }
-
- @Test
- public void testFeedListDownloadUrls() {
- List<Feed> feeds = saveFeedlist(10, 0, false);
- List<String> urls = DBReader.getFeedListDownloadUrls();
- assertNotNull(urls);
- assertEquals(feeds.size(), urls.size());
- for (int i = 0; i < urls.size(); i++) {
- assertEquals(urls.get(i), feeds.get(i).getDownload_url());
- }
- }
-
- @Test
- public void testLoadFeedDataOfFeedItemlist() {
- final int numFeeds = 10;
- final int numItems = 1;
- List<Feed> feeds = saveFeedlist(numFeeds, numItems, false);
- List<FeedItem> items = new ArrayList<>();
- for (Feed f : feeds) {
- for (FeedItem item : f.getItems()) {
- item.setFeed(null);
- item.setFeedId(f.getId());
- items.add(item);
- }
- }
- DBReader.loadAdditionalFeedItemListData(items);
- for (int i = 0; i < numFeeds; i++) {
- for (int j = 0; j < numItems; j++) {
- FeedItem item = feeds.get(i).getItems().get(j);
- assertNotNull(item.getFeed());
- assertEquals(feeds.get(i).getId(), item.getFeed().getId());
- assertEquals(item.getFeed().getId(), item.getFeedId());
- }
- }
- }
-
- @Test
- public void testGetFeedItemList() {
- final int numFeeds = 1;
- final int numItems = 10;
- Feed feed = saveFeedlist(numFeeds, numItems, false).get(0);
- List<FeedItem> items = feed.getItems();
- feed.setItems(null);
- List<FeedItem> savedItems = DBReader.getFeedItemList(feed);
- assertNotNull(savedItems);
- assertEquals(items.size(), savedItems.size());
- for (int i = 0; i < savedItems.size(); i++) {
- assertEquals(savedItems.get(i).getId(), items.get(i).getId());
- }
- }
-
- private List<FeedItem> saveQueue(int numItems) {
- if (numItems <= 0) {
- throw new IllegalArgumentException("numItems<=0");
- }
- List<Feed> feeds = saveFeedlist(numItems, numItems, false);
- List<FeedItem> allItems = new ArrayList<>();
- for (Feed f : feeds) {
- allItems.addAll(f.getItems());
- }
- // take random items from every feed
- Random random = new Random();
- List<FeedItem> queue = new ArrayList<>();
- while (queue.size() < numItems) {
- int index = random.nextInt(numItems);
- if (!queue.contains(allItems.get(index))) {
- queue.add(allItems.get(index));
- }
- }
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- return queue;
- }
-
- @Test
- public void testGetQueueIDList() {
- final int numItems = 10;
- List<FeedItem> queue = saveQueue(numItems);
- LongList ids = DBReader.getQueueIDList();
- assertNotNull(ids);
- assertEquals(ids.size(), queue.size());
- for (int i = 0; i < queue.size(); i++) {
- assertTrue(ids.get(i) != 0);
- assertEquals(ids.get(i), queue.get(i).getId());
- }
- }
-
- @Test
- public void testGetQueue() {
- final int numItems = 10;
- List<FeedItem> queue = saveQueue(numItems);
- List<FeedItem> savedQueue = DBReader.getQueue();
- assertNotNull(savedQueue);
- assertEquals(savedQueue.size(), queue.size());
- for (int i = 0; i < queue.size(); i++) {
- assertTrue(savedQueue.get(i).getId() != 0);
- assertEquals(savedQueue.get(i).getId(), queue.get(i).getId());
- }
- }
-
- private List<FeedItem> saveDownloadedItems(int numItems) {
- if (numItems <= 0) {
- throw new IllegalArgumentException("numItems<=0");
- }
- List<Feed> feeds = saveFeedlist(numItems, numItems, true);
- List<FeedItem> items = new ArrayList<>();
- for (Feed f : feeds) {
- items.addAll(f.getItems());
- }
- List<FeedItem> downloaded = new ArrayList<>();
- Random random = new Random();
-
- while (downloaded.size() < numItems) {
- int i = random.nextInt(numItems);
- if (!downloaded.contains(items.get(i))) {
- FeedItem item = items.get(i);
- item.getMedia().setDownloaded(true);
- item.getMedia().setFile_url("file" + i);
- downloaded.add(item);
- }
- }
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItemlist(downloaded);
- adapter.close();
- return downloaded;
- }
-
- @Test
- public void testGetDownloadedItems() {
- final int numItems = 10;
- List<FeedItem> downloaded = saveDownloadedItems(numItems);
- List<FeedItem> downloaded_saved = DBReader.getDownloadedItems();
- assertNotNull(downloaded_saved);
- assertEquals(downloaded.size(), downloaded_saved.size());
- for (FeedItem item : downloaded_saved) {
- assertNotNull(item.getMedia());
- assertTrue(item.getMedia().isDownloaded());
- assertNotNull(item.getMedia().getDownload_url());
- }
- }
-
- private List<FeedItem> saveNewItems(int numItems) {
- List<Feed> feeds = saveFeedlist(numItems, numItems, true);
- List<FeedItem> items = new ArrayList<>();
- for (Feed f : feeds) {
- items.addAll(f.getItems());
- }
- List<FeedItem> newItems = new ArrayList<>();
- Random random = new Random();
-
- while (newItems.size() < numItems) {
- int i = random.nextInt(numItems);
- if (!newItems.contains(items.get(i))) {
- FeedItem item = items.get(i);
- item.setNew();
- newItems.add(item);
- }
- }
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setFeedItemlist(newItems);
- adapter.close();
- return newItems;
- }
-
- @Test
- public void testGetNewItemIds() {
- final int numItems = 10;
-
- List<FeedItem> newItems = saveNewItems(numItems);
- long[] unreadIds = new long[newItems.size()];
- for (int i = 0; i < newItems.size(); i++) {
- unreadIds[i] = newItems.get(i).getId();
- }
- List<FeedItem> newItemsSaved = DBReader.getNewItemsList(0, Integer.MAX_VALUE);
- assertNotNull(newItemsSaved);
- assertEquals(newItemsSaved.size(), newItems.size());
- for (FeedItem feedItem : newItemsSaved) {
- long savedId = feedItem.getId();
- boolean found = false;
- for (long id : unreadIds) {
- if (id == savedId) {
- found = true;
- break;
- }
- }
- assertTrue(found);
- }
- }
-
- @Test
- public void testGetPlaybackHistory() {
- final int numItems = (DBReader.PLAYBACK_HISTORY_SIZE + 1) * 2;
- final int playedItems = DBReader.PLAYBACK_HISTORY_SIZE + 1;
- final int numReturnedItems = Math.min(playedItems, DBReader.PLAYBACK_HISTORY_SIZE);
- final int numFeeds = 1;
-
- Feed feed = DBTestUtils.saveFeedlist(numFeeds, numItems, true).get(0);
- long[] ids = new long[playedItems];
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- for (int i = 0; i < playedItems; i++) {
- FeedMedia m = feed.getItems().get(i).getMedia();
- m.setPlaybackCompletionDate(new Date(i + 1));
- adapter.setFeedMediaPlaybackCompletionDate(m);
- ids[ids.length - 1 - i] = m.getItem().getId();
- }
- adapter.close();
-
- List<FeedItem> saved = DBReader.getPlaybackHistory();
- assertNotNull(saved);
- assertEquals("Wrong size: ", numReturnedItems, saved.size());
- for (int i = 0; i < numReturnedItems; i++) {
- FeedItem item = saved.get(i);
- assertNotNull(item.getMedia().getPlaybackCompletionDate());
- assertEquals("Wrong sort order: ", item.getId(), ids[i]);
- }
- }
-
- @Test
- public void testGetNavDrawerDataQueueEmptyNoUnreadItems() {
- final int NUM_FEEDS = 10;
- final int NUM_ITEMS = 10;
- DBTestUtils.saveFeedlist(NUM_FEEDS, NUM_ITEMS, true);
- DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData();
- assertEquals(NUM_FEEDS, navDrawerData.feeds.size());
- assertEquals(0, navDrawerData.numNewItems);
- assertEquals(0, navDrawerData.queueSize);
- }
-
- @Test
- public void testGetNavDrawerDataQueueNotEmptyWithUnreadItems() {
- final int NUM_FEEDS = 10;
- final int NUM_ITEMS = 10;
- final int NUM_QUEUE = 1;
- final int NUM_NEW = 2;
- List<Feed> feeds = DBTestUtils.saveFeedlist(NUM_FEEDS, NUM_ITEMS, true);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- for (int i = 0; i < NUM_NEW; i++) {
- FeedItem item = feeds.get(0).getItems().get(i);
- item.setNew();
- adapter.setSingleFeedItem(item);
- }
- List<FeedItem> queue = new ArrayList<>();
- for (int i = 0; i < NUM_QUEUE; i++) {
- FeedItem item = feeds.get(1).getItems().get(i);
- queue.add(item);
- }
- adapter.setQueue(queue);
-
- adapter.close();
-
- DBReader.NavDrawerData navDrawerData = DBReader.getNavDrawerData();
- assertEquals(NUM_FEEDS, navDrawerData.feeds.size());
- assertEquals(NUM_NEW, navDrawerData.numNewItems);
- assertEquals(NUM_QUEUE, navDrawerData.queueSize);
- }
-
- @Test
- public void testGetFeedItemlistCheckChaptersFalse() throws Exception {
- List<Feed> feeds = DBTestUtils.saveFeedlist(10, 10, false, false, 0);
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- assertFalse(item.hasChapters());
- }
- }
- }
-
- @Test
- public void testGetFeedItemlistCheckChaptersTrue() throws Exception {
- List<Feed> feeds = saveFeedlist(10, 10, false, true, 10);
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.hasChapters());
- }
- }
- }
-
- @Test
- public void testLoadChaptersOfFeedItemNoChapters() throws Exception {
- List<Feed> feeds = saveFeedlist(1, 3, false, false, 0);
- saveFeedlist(1, 3, false, true, 3);
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- assertFalse(item.hasChapters());
- item.setChapters(DBReader.loadChaptersOfFeedItem(item));
- assertFalse(item.hasChapters());
- assertNull(item.getChapters());
- }
- }
- }
-
- @Test
- public void testLoadChaptersOfFeedItemWithChapters() throws Exception {
- final int NUM_CHAPTERS = 3;
- DBTestUtils.saveFeedlist(1, 3, false, false, 0);
- List<Feed> feeds = saveFeedlist(1, 3, false, true, NUM_CHAPTERS);
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.hasChapters());
- item.setChapters(DBReader.loadChaptersOfFeedItem(item));
- assertTrue(item.hasChapters());
- assertNotNull(item.getChapters());
- assertEquals(NUM_CHAPTERS, item.getChapters().size());
- }
- }
- }
-
- @Test
- public void testGetItemWithChapters() throws Exception {
- final int NUM_CHAPTERS = 3;
- List<Feed> feeds = saveFeedlist(1, 1, false, true, NUM_CHAPTERS);
- FeedItem item1 = feeds.get(0).getItems().get(0);
- FeedItem item2 = DBReader.getFeedItem(item1.getId());
- item2.setChapters(DBReader.loadChaptersOfFeedItem(item2));
- assertTrue(item2.hasChapters());
- assertEquals(item1.getChapters(), item2.getChapters());
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java
deleted file mode 100644
index c28ce5003..000000000
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java
+++ /dev/null
@@ -1,289 +0,0 @@
-package de.test.antennapod.storage;
-
-import android.content.Context;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.DBTasks;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-
-import static de.danoeh.antennapod.core.util.FeedItemUtil.getIdList;
-import static java.util.Collections.singletonList;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class for DBTasks
- */
-@SmallTest
-public class DBTasksTest {
- private Context context;
-
- @After
- public void tearDown() throws Exception {
- assertTrue(PodDBAdapter.deleteDatabase());
- }
-
- @Before
- public void setUp() throws Exception {
- context = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
- // create new database
- PodDBAdapter.init(context);
- PodDBAdapter.deleteDatabase();
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.close();
-
- UserPreferences.init(context);
- }
-
- @Test
- public void testUpdateFeedNewFeed() {
- final int NUM_ITEMS = 10;
-
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < NUM_ITEMS; i++) {
- feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(), FeedItem.UNPLAYED, feed));
- }
- Feed newFeed = DBTasks.updateFeed(context, feed, false);
-
- assertEquals(feed.getId(), newFeed.getId());
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertFalse(item.isPlayed());
- assertTrue(item.getId() != 0);
- }
- }
-
- /** Two feeds with the same title, but different download URLs should be treated as different feeds. */
- @Test
- public void testUpdateFeedSameTitle() {
-
- Feed feed1 = new Feed("url1", null, "title");
- Feed feed2 = new Feed("url2", null, "title");
-
- feed1.setItems(new ArrayList<>());
- feed2.setItems(new ArrayList<>());
-
- Feed savedFeed1 = DBTasks.updateFeed(context, feed1, false);
- Feed savedFeed2 = DBTasks.updateFeed(context, feed2, false);
-
- assertTrue(savedFeed1.getId() != savedFeed2.getId());
- }
-
- @Test
- public void testUpdateFeedUpdatedFeed() {
- final int NUM_ITEMS_OLD = 10;
- final int NUM_ITEMS_NEW = 10;
-
- final Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < NUM_ITEMS_OLD; i++) {
- feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(i), FeedItem.PLAYED, feed));
- }
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- // ensure that objects have been saved in db, then reset
- assertTrue(feed.getId() != 0);
- final long feedID = feed.getId();
- feed.setId(0);
- List<Long> itemIDs = new ArrayList<>();
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- itemIDs.add(item.getId());
- item.setId(0);
- }
-
- for (int i = NUM_ITEMS_OLD; i < NUM_ITEMS_NEW + NUM_ITEMS_OLD; i++) {
- feed.getItems().add(0, new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(i), FeedItem.UNPLAYED, feed));
- }
-
- final Feed newFeed = DBTasks.updateFeed(context, feed, false);
- assertNotSame(newFeed, feed);
-
- updatedFeedTest(newFeed, feedID, itemIDs, NUM_ITEMS_OLD, NUM_ITEMS_NEW);
-
- final Feed feedFromDB = DBReader.getFeed(newFeed.getId());
- assertNotNull(feedFromDB);
- assertEquals(newFeed.getId(), feedFromDB.getId());
- updatedFeedTest(feedFromDB, feedID, itemIDs, NUM_ITEMS_OLD, NUM_ITEMS_NEW);
- }
-
- @Test
- public void testUpdateFeedMediaUrlResetState() {
- final Feed feed = new Feed("url", null, "title");
- FeedItem item = new FeedItem(0, "item", "id", "link", new Date(), FeedItem.PLAYED, feed);
- feed.setItems(singletonList(item));
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- // ensure that objects have been saved in db, then reset
- assertTrue(feed.getId() != 0);
- assertTrue(item.getId() != 0);
-
- FeedMedia media = new FeedMedia(item, "url", 1024, "mime/type");
- item.setMedia(media);
- List<FeedItem> list = new ArrayList<>();
- list.add(item);
- feed.setItems(list);
-
- final Feed newFeed = DBTasks.updateFeed(context, feed, false);
- assertNotSame(newFeed, feed);
-
- final Feed feedFromDB = DBReader.getFeed(newFeed.getId());
- final FeedItem feedItemFromDB = feedFromDB.getItems().get(0);
- assertTrue("state: " + feedItemFromDB.getState(), feedItemFromDB.isNew());
- }
-
- @Test
- public void testUpdateFeedRemoveUnlistedItems() {
- final Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < 10; i++) {
- feed.getItems().add(
- new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(i), FeedItem.PLAYED, feed));
- }
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- // delete some items
- feed.getItems().subList(0, 2).clear();
- Feed newFeed = DBTasks.updateFeed(context, feed, true);
- assertEquals(8, newFeed.getItems().size()); // 10 - 2 = 8 items
-
- Feed feedFromDB = DBReader.getFeed(newFeed.getId());
- assertEquals(8, feedFromDB.getItems().size()); // 10 - 2 = 8 items
- }
-
- private void updatedFeedTest(final Feed newFeed, long feedID, List<Long> itemIDs, final int NUM_ITEMS_OLD, final int NUM_ITEMS_NEW) {
- assertEquals(feedID, newFeed.getId());
- assertEquals(NUM_ITEMS_NEW + NUM_ITEMS_OLD, newFeed.getItems().size());
- Collections.reverse(newFeed.getItems());
- Date lastDate = new Date(0);
- for (int i = 0; i < NUM_ITEMS_OLD; i++) {
- FeedItem item = newFeed.getItems().get(i);
- assertSame(newFeed, item.getFeed());
- assertEquals((long) itemIDs.get(i), item.getId());
- assertTrue(item.isPlayed());
- assertTrue(item.getPubDate().getTime() >= lastDate.getTime());
- lastDate = item.getPubDate();
- }
- for (int i = NUM_ITEMS_OLD; i < NUM_ITEMS_NEW + NUM_ITEMS_OLD; i++) {
- FeedItem item = newFeed.getItems().get(i);
- assertSame(newFeed, item.getFeed());
- assertTrue(item.getId() != 0);
- assertFalse(item.isPlayed());
- assertTrue(item.getPubDate().getTime() >= lastDate.getTime());
- lastDate = item.getPubDate();
- }
- }
-
- @Test
- public void testAddQueueItemsInDownload_EnqueueEnabled() throws Exception {
- // Setup test data / environment
- UserPreferences.setEnqueueDownloadedEpisodes(true);
- UserPreferences.setEnqueueLocation(UserPreferences.EnqueueLocation.BACK);
-
- List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems();
- List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems();
-
- DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get();
- // the first item fis1.get(0) is already in the queue
- FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) };
-
- // Expectations:
- List<FeedItem> expectedEnqueued = Arrays.asList(fis1.get(1), fis2.get(2), fis2.get(1));
- List<FeedItem> expectedQueue = new ArrayList<>();
- expectedQueue.addAll(DBReader.getQueue());
- expectedQueue.addAll(expectedEnqueued);
-
- // Run actual test and assert results
- List<? extends FeedItem> actualEnqueued =
- DBTasks.enqueueFeedItemsToDownload(context, Arrays.asList(itemsToDownload));
-
- assertEqualsByIds("Only items not in the queue are enqueued", expectedEnqueued, actualEnqueued);
- assertEqualsByIds("Queue has new items appended", expectedQueue, DBReader.getQueue());
- }
-
- @Test
- public void testAddQueueItemsInDownload_EnqueueDisabled() throws Exception {
- // Setup test data / environment
- UserPreferences.setEnqueueDownloadedEpisodes(false);
-
- List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems();
- List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems();
-
- DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get();
- FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) };
-
- // Expectations:
- List<FeedItem> expectedEnqueued = Collections.emptyList();
- List<FeedItem> expectedQueue = DBReader.getQueue();
-
- // Run actual test and assert results
- List<? extends FeedItem> actualEnqueued =
- DBTasks.enqueueFeedItemsToDownload(context, Arrays.asList(itemsToDownload));
-
- assertEqualsByIds("No item is enqueued", expectedEnqueued, actualEnqueued);
- assertEqualsByIds("Queue is unchanged", expectedQueue, DBReader.getQueue());
- }
-
- private void assertEqualsByIds(String msg, List<? extends FeedItem> expected, List<? extends FeedItem> actual) {
- // assert only the IDs, so that any differences are easily to spot.
- List<Long> expectedIds = getIdList(expected);
- List<Long> actualIds = getIdList(actual);
- assertEquals(msg, expectedIds, actualIds);
- }
-
- private Feed createSavedFeed(String title, int numFeedItems) {
- final Feed feed = new Feed("url", null, title);
-
- if (numFeedItems > 0) {
- List<FeedItem> items = new ArrayList<>(numFeedItems);
- for (int i = 1; i <= numFeedItems; i++) {
- FeedItem item = new FeedItem(0, "item " + i + " of " + title, "id", "link",
- new Date(), FeedItem.UNPLAYED, feed);
- items.add(item);
- }
- feed.setItems(items);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
- return feed;
- }
-
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java
deleted file mode 100644
index 840a7d01f..000000000
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBTestUtils.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package de.test.antennapod.storage;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-import de.danoeh.antennapod.core.feed.Chapter;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.feed.SimpleChapter;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
-
-import static org.junit.Assert.assertTrue;
-
-/**
- * Utility methods for DB* tests.
- */
-class DBTestUtils {
-
- private DBTestUtils(){}
- /**
- * Use this method when tests don't involve chapters.
- */
- public static List<Feed> saveFeedlist(int numFeeds, int numItems, boolean withMedia) {
- return saveFeedlist(numFeeds, numItems, withMedia, false, 0);
- }
-
- /**
- * Use this method when tests involve chapters.
- */
- public static List<Feed> saveFeedlist(int numFeeds, int numItems, boolean withMedia,
- boolean withChapters, int numChapters) {
- if (numFeeds <= 0) {
- throw new IllegalArgumentException("numFeeds<=0");
- }
- if (numItems < 0) {
- throw new IllegalArgumentException("numItems<0");
- }
-
- List<Feed> feeds = new ArrayList<>();
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- for (int i = 0; i < numFeeds; i++) {
- Feed f = new Feed(0, null, "feed " + i, "link" + i, "descr", null, null,
- null, null, "id" + i, null, null, "url" + i, false);
- f.setItems(new ArrayList<>());
- for (int j = 0; j < numItems; j++) {
- FeedItem item = new FeedItem(0, "item " + j, "id" + j, "link" + j, new Date(),
- FeedItem.PLAYED, f, withChapters);
- if (withMedia) {
- FeedMedia media = new FeedMedia(item, "url" + j, 1, "audio/mp3");
- item.setMedia(media);
- }
- if (withChapters) {
- List<Chapter> chapters = new ArrayList<>();
- item.setChapters(chapters);
- for (int k = 0; k < numChapters; k++) {
- chapters.add(new SimpleChapter(k, "item " + j + " chapter " + k,
- "http://example.com", "http://example.com/image.png"));
- }
- }
- f.getItems().add(item);
- }
- Collections.sort(f.getItems(), new FeedItemPubdateComparator());
- adapter.setCompleteFeed(f);
- assertTrue(f.getId() != 0);
- for (FeedItem item : f.getItems()) {
- assertTrue(item.getId() != 0);
- }
- feeds.add(f);
- }
- adapter.close();
-
- return feeds;
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java
deleted file mode 100644
index 652389d00..000000000
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java
+++ /dev/null
@@ -1,857 +0,0 @@
-package de.test.antennapod.storage;
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import androidx.preference.PreferenceManager;
-import android.util.Log;
-
-import androidx.core.util.Consumer;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.MediumTest;
-
-import org.awaitility.Awaitility;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.storage.DBWriter;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-import de.danoeh.antennapod.core.util.FeedItemUtil;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class for DBWriter
- */
-@MediumTest
-public class DBWriterTest {
-
- private static final String TAG = "DBWriterTest";
- private static final String TEST_FOLDER = "testDBWriter";
- private static final long TIMEOUT = 5L;
-
- @After
- public void tearDown() throws Exception {
- assertTrue(PodDBAdapter.deleteDatabase());
-
- final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- File testDir = context.getExternalFilesDir(TEST_FOLDER);
- assertNotNull(testDir);
- for (File f : testDir.listFiles()) {
- f.delete();
- }
- }
-
- @Before
- public void setUp() throws Exception {
- // create new database
- PodDBAdapter.init(InstrumentationRegistry.getInstrumentation().getTargetContext());
- PodDBAdapter.deleteDatabase();
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.close();
-
- Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).edit();
- prefEdit.putBoolean(UserPreferences.PREF_DELETE_REMOVES_FROM_QUEUE, true).commit();
-
- UserPreferences.init(context);
- }
-
- @Test
- public void testSetFeedMediaPlaybackInformation()
- throws IOException, ExecutionException, InterruptedException, TimeoutException {
- final int POSITION = 50;
- final long LAST_PLAYED_TIME = 1000;
- final int PLAYED_DURATION = 60;
- final int DURATION = 100;
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- FeedItem item = new FeedItem(0, "Item", "Item", "url", new Date(), FeedItem.PLAYED, feed);
- items.add(item);
- FeedMedia media = new FeedMedia(0, item, DURATION, 1, 1, "mime_type", "dummy path", "download_url", true, null, 0, 0);
- item.setMedia(media);
-
- DBWriter.setFeedItem(item).get(TIMEOUT, TimeUnit.SECONDS);
-
- media.setPosition(POSITION);
- media.setLastPlayedTime(LAST_PLAYED_TIME);
- media.setPlayedDuration(PLAYED_DURATION);
-
- DBWriter.setFeedMediaPlaybackInformation(item.getMedia()).get(TIMEOUT, TimeUnit.SECONDS);
-
- FeedItem itemFromDb = DBReader.getFeedItem(item.getId());
- FeedMedia mediaFromDb = itemFromDb.getMedia();
-
- assertEquals(POSITION, mediaFromDb.getPosition());
- assertEquals(LAST_PLAYED_TIME, mediaFromDb.getLastPlayedTime());
- assertEquals(PLAYED_DURATION, mediaFromDb.getPlayedDuration());
- assertEquals(DURATION, mediaFromDb.getDuration());
- }
-
- @Test
- public void testDeleteFeedMediaOfItemFileExists()
- throws IOException, ExecutionException, InterruptedException, TimeoutException {
- File dest = new File(InstrumentationRegistry
- .getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER), "testFile");
-
- assertTrue(dest.createNewFile());
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- FeedItem item = new FeedItem(0, "Item", "Item", "url", new Date(), FeedItem.PLAYED, feed);
-
- FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", dest.getAbsolutePath(), "download_url", true, null, 0, 0);
- item.setMedia(media);
-
- items.add(item);
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
- assertTrue(media.getId() != 0);
- assertTrue(item.getId() != 0);
-
- DBWriter.deleteFeedMediaOfItem(InstrumentationRegistry.getInstrumentation().getTargetContext(), media.getId())
- .get(TIMEOUT, TimeUnit.SECONDS);
- media = DBReader.getFeedMedia(media.getId());
- assertNotNull(media);
- assertFalse(dest.exists());
- assertFalse(media.isDownloaded());
- assertNull(media.getFile_url());
- }
-
- @Test
- public void testDeleteFeedMediaOfItemRemoveFromQueue()
- throws IOException, ExecutionException, InterruptedException, TimeoutException {
- assertTrue(UserPreferences.shouldDeleteRemoveFromQueue());
-
- File dest = new File(InstrumentationRegistry
- .getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER), "testFile");
-
- assertTrue(dest.createNewFile());
-
- Feed feed = new Feed("url", null, "title");
- List<FeedItem> items = new ArrayList<>();
- List<FeedItem> queue = new ArrayList<>();
- feed.setItems(items);
- FeedItem item = new FeedItem(0, "Item", "Item", "url", new Date(), FeedItem.UNPLAYED, feed);
-
- FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", dest.getAbsolutePath(), "download_url", true, null, 0, 0);
- item.setMedia(media);
-
- items.add(item);
- queue.add(item);
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.setQueue(queue);
- adapter.close();
- assertTrue(media.getId() != 0);
- assertTrue(item.getId() != 0);
- queue = DBReader.getQueue();
- assertTrue(queue.size() != 0);
-
- DBWriter.deleteFeedMediaOfItem(InstrumentationRegistry.getInstrumentation().getTargetContext(), media.getId());
- Awaitility.await().until(() -> !dest.exists());
- media = DBReader.getFeedMedia(media.getId());
- assertNotNull(media);
- assertFalse(dest.exists());
- assertFalse(media.isDownloaded());
- assertNull(media.getFile_url());
- queue = DBReader.getQueue();
- assertEquals(0, queue.size());
- }
-
- @Test
- public void testDeleteFeed() throws ExecutionException, InterruptedException, IOException, TimeoutException {
- File destFolder = InstrumentationRegistry.getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
- assertNotNull(destFolder);
-
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
-
- List<File> itemFiles = new ArrayList<>();
- // create items with downloaded media files
- for (int i = 0; i < 10; i++) {
- FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
-
- File enc = new File(destFolder, "file " + i);
- assertTrue(enc.createNewFile());
-
- itemFiles.add(enc);
- FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null, 0, 0);
- item.setMedia(media);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- assertTrue(item.getMedia().getId() != 0);
- }
-
- DBWriter.deleteFeed(InstrumentationRegistry
- .getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
-
- // check if files still exist
- for (File f : itemFiles) {
- assertFalse(f.exists());
- }
-
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor c = adapter.getFeedCursor(feed.getId());
- assertEquals(0, c.getCount());
- c.close();
- for (FeedItem item : feed.getItems()) {
- c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
- assertEquals(0, c.getCount());
- c.close();
- c = adapter.getSingleFeedMediaCursor(item.getMedia().getId());
- assertEquals(0, c.getCount());
- c.close();
- }
- adapter.close();
- }
-
- @Test
- public void testDeleteFeedNoItems() throws IOException, ExecutionException, InterruptedException, TimeoutException {
- File destFolder = InstrumentationRegistry.getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
- assertNotNull(destFolder);
-
- Feed feed = new Feed("url", null, "title");
- feed.setItems(null);
- feed.setImageUrl("url");
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
-
- DBWriter.deleteFeed(InstrumentationRegistry
- .getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
-
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor c = adapter.getFeedCursor(feed.getId());
- assertEquals(0, c.getCount());
- c.close();
- adapter.close();
- }
-
- @Test
- public void testDeleteFeedNoFeedMedia() throws IOException, ExecutionException, InterruptedException, TimeoutException {
- File destFolder = InstrumentationRegistry.getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
- assertNotNull(destFolder);
-
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
-
- feed.setImageUrl("url");
-
- // create items
- for (int i = 0; i < 10; i++) {
- FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
-
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- }
-
- DBWriter.deleteFeed(InstrumentationRegistry
- .getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
-
-
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor c = adapter.getFeedCursor(feed.getId());
- assertEquals(0, c.getCount());
- c.close();
- for (FeedItem item : feed.getItems()) {
- c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
- assertEquals(0, c.getCount());
- c.close();
- }
- adapter.close();
- }
-
- @Test
- public void testDeleteFeedWithQueueItems() throws ExecutionException, InterruptedException, TimeoutException {
- File destFolder = InstrumentationRegistry.getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
- assertNotNull(destFolder);
-
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
-
- feed.setImageUrl("url");
-
- // create items with downloaded media files
- for (int i = 0; i < 10; i++) {
- FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
- File enc = new File(destFolder, "file " + i);
- FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", false, null, 0, 0);
- item.setMedia(media);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- assertTrue(item.getMedia().getId() != 0);
- }
-
-
- List<FeedItem> queue = new ArrayList<>(feed.getItems());
- adapter.open();
- adapter.setQueue(queue);
-
- Cursor queueCursor = adapter.getQueueIDCursor();
- assertEquals(queue.size(), queueCursor.getCount());
- queueCursor.close();
-
- adapter.close();
- DBWriter.deleteFeed(InstrumentationRegistry
- .getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
- adapter.open();
-
- Cursor c = adapter.getFeedCursor(feed.getId());
- assertEquals(0, c.getCount());
- c.close();
- for (FeedItem item : feed.getItems()) {
- c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
- assertEquals(0, c.getCount());
- c.close();
- c = adapter.getSingleFeedMediaCursor(item.getMedia().getId());
- assertEquals(0, c.getCount());
- c.close();
- }
- c = adapter.getQueueCursor();
- assertEquals(0, c.getCount());
- c.close();
- adapter.close();
- }
-
- @Test
- public void testDeleteFeedNoDownloadedFiles() throws ExecutionException, InterruptedException, TimeoutException {
- File destFolder = InstrumentationRegistry.getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
- assertNotNull(destFolder);
-
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
-
- feed.setImageUrl("url");
-
- // create items with downloaded media files
- for (int i = 0; i < 10; i++) {
- FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
- File enc = new File(destFolder, "file " + i);
- FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", false, null, 0, 0);
- item.setMedia(media);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- assertTrue(item.getMedia().getId() != 0);
- }
-
- DBWriter.deleteFeed(InstrumentationRegistry
- .getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
-
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor c = adapter.getFeedCursor(feed.getId());
- assertEquals(0, c.getCount());
- c.close();
- for (FeedItem item : feed.getItems()) {
- c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
- assertEquals(0, c.getCount());
- c.close();
- c = adapter.getSingleFeedMediaCursor(item.getMedia().getId());
- assertEquals(0, c.getCount());
- c.close();
- }
- adapter.close();
- }
-
- @Test
- public void testDeleteFeedItems() throws Exception {
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- feed.setImageUrl("url");
-
- // create items
- for (int i = 0; i < 10; i++) {
- FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- List<FeedItem> itemsToDelete = feed.getItems().subList(0, 2);
- DBWriter.deleteFeedItems(InstrumentationRegistry.getInstrumentation()
- .getTargetContext(), itemsToDelete).get(TIMEOUT, TimeUnit.SECONDS);
-
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- for (int i = 0; i < feed.getItems().size(); i++) {
- FeedItem feedItem = feed.getItems().get(i);
- Cursor c = adapter.getFeedItemCursor(String.valueOf(feedItem.getId()));
- if (i < 2) {
- assertEquals(0, c.getCount());
- } else {
- assertEquals(1, c.getCount());
- }
- c.close();
- }
- adapter.close();
- }
-
- private FeedMedia playbackHistorySetup(Date playbackCompletionDate) {
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
- FeedMedia media = new FeedMedia(0, item, 10, 0, 1, "mime", null, "url", false, playbackCompletionDate, 0, 0);
- feed.getItems().add(item);
- item.setMedia(media);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
- assertTrue(media.getId() != 0);
- return media;
- }
-
- @Test
- public void testAddItemToPlaybackHistoryNotPlayedYet()
- throws ExecutionException, InterruptedException, TimeoutException {
- FeedMedia media = playbackHistorySetup(null);
- DBWriter.addItemToPlaybackHistory(media).get(TIMEOUT, TimeUnit.SECONDS);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- media = DBReader.getFeedMedia(media.getId());
- adapter.close();
-
- assertNotNull(media);
- assertNotNull(media.getPlaybackCompletionDate());
- }
-
- @Test
- public void testAddItemToPlaybackHistoryAlreadyPlayed()
- throws ExecutionException, InterruptedException, TimeoutException {
- final long OLD_DATE = 0;
-
- FeedMedia media = playbackHistorySetup(new Date(OLD_DATE));
- DBWriter.addItemToPlaybackHistory(media).get(TIMEOUT, TimeUnit.SECONDS);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- media = DBReader.getFeedMedia(media.getId());
- adapter.close();
-
- assertNotNull(media);
- assertNotNull(media.getPlaybackCompletionDate());
- assertNotEquals(media.getPlaybackCompletionDate().getTime(), OLD_DATE);
- }
-
- private Feed queueTestSetupMultipleItems(final int numItems) throws InterruptedException, ExecutionException, TimeoutException {
- final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- UserPreferences.setEnqueueLocation(UserPreferences.EnqueueLocation.BACK);
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < numItems; i++) {
- FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- }
- List<Future<?>> futures = new ArrayList<>();
- for (FeedItem item : feed.getItems()) {
- futures.add(DBWriter.addQueueItem(context, item));
- }
- for (Future<?> f : futures) {
- f.get(TIMEOUT, TimeUnit.SECONDS);
- }
- return feed;
- }
-
- @Test
- public void testAddQueueItemSingleItem() throws InterruptedException, ExecutionException, TimeoutException {
- final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(item.getId() != 0);
- DBWriter.addQueueItem(context, item).get(TIMEOUT, TimeUnit.SECONDS);
-
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor cursor = adapter.getQueueIDCursor();
- assertTrue(cursor.moveToFirst());
- assertEquals(item.getId(), cursor.getLong(0));
- cursor.close();
- adapter.close();
- }
-
- @Test
- public void testAddQueueItemSingleItemAlreadyInQueue() throws InterruptedException, ExecutionException, TimeoutException {
- final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(item.getId() != 0);
- DBWriter.addQueueItem(context, item).get(TIMEOUT, TimeUnit.SECONDS);
-
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor cursor = adapter.getQueueIDCursor();
- assertTrue(cursor.moveToFirst());
- assertEquals(item.getId(), cursor.getLong(0));
- cursor.close();
- adapter.close();
-
- DBWriter.addQueueItem(context, item).get(TIMEOUT, TimeUnit.SECONDS);
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- cursor = adapter.getQueueIDCursor();
- assertTrue(cursor.moveToFirst());
- assertEquals(item.getId(), cursor.getLong(0));
- assertEquals(1, cursor.getCount());
- cursor.close();
- adapter.close();
- }
-
- @Test
- public void testAddQueueItemMultipleItems() throws InterruptedException, ExecutionException, TimeoutException {
- final int NUM_ITEMS = 10;
-
- Feed feed = queueTestSetupMultipleItems(NUM_ITEMS);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor cursor = adapter.getQueueIDCursor();
- assertTrue(cursor.moveToFirst());
- assertEquals(NUM_ITEMS, cursor.getCount());
- List<Long> expectedIds = FeedItemUtil.getIdList(feed.getItems());
- List<Long> actualIds = new ArrayList<>();
- for (int i = 0; i < NUM_ITEMS; i++) {
- assertTrue(cursor.moveToPosition(i));
- actualIds.add(cursor.getLong(0));
- }
- cursor.close();
- adapter.close();
- assertEquals("Bulk add to queue: result order should be the same as the order given",
- expectedIds, actualIds);
- }
-
- @Test
- public void testClearQueue() throws InterruptedException, ExecutionException, TimeoutException {
- final int NUM_ITEMS = 10;
-
- queueTestSetupMultipleItems(NUM_ITEMS);
- DBWriter.clearQueue().get(TIMEOUT, TimeUnit.SECONDS);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor cursor = adapter.getQueueIDCursor();
- assertFalse(cursor.moveToFirst());
- cursor.close();
- adapter.close();
- }
-
- @Test
- public void testRemoveQueueItem() throws InterruptedException, ExecutionException, TimeoutException {
- final int NUM_ITEMS = 10;
- final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- Feed feed = createTestFeed(NUM_ITEMS);
-
- for (int removeIndex = 0; removeIndex < NUM_ITEMS; removeIndex++) {
- final FeedItem item = feed.getItems().get(removeIndex);
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setQueue(feed.getItems());
- adapter.close();
-
- DBWriter.removeQueueItem(context, false, item).get(TIMEOUT, TimeUnit.SECONDS);
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor queue = adapter.getQueueIDCursor();
- assertEquals(NUM_ITEMS - 1, queue.getCount());
- for (int i = 0; i < queue.getCount(); i++) {
- assertTrue(queue.moveToPosition(i));
- final long queueID = queue.getLong(0);
- assertTrue(queueID != item.getId()); // removed item is no longer in queue
- boolean idFound = false;
- for (FeedItem other : feed.getItems()) { // items that were not removed are still in the queue
- idFound = idFound | (other.getId() == queueID);
- }
- assertTrue(idFound);
- }
- queue.close();
- adapter.close();
- }
- }
-
- @Test
- public void testRemoveQueueItemMultipleItems() throws InterruptedException, ExecutionException, TimeoutException {
- // Setup test data
- //
- final int NUM_ITEMS = 5;
- final int NUM_IN_QUEUE = NUM_ITEMS - 1; // the last one not in queue for boundary condition
- final Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- Feed feed = createTestFeed(NUM_ITEMS);
-
- List<FeedItem> itemsToAdd = feed.getItems().subList(0, NUM_IN_QUEUE);
- withPodDB(adapter -> adapter.setQueue(itemsToAdd) );
-
- // Actual tests
- //
-
- // Use array rather than List to make codes more succinct
- Long[] itemIds = toItemIds(feed.getItems()).toArray(new Long[0]);
-
- DBWriter.removeQueueItem(context, false,
- itemIds[1], itemIds[3]).get(TIMEOUT, TimeUnit.SECONDS);
- assertQueueByItemIds("Average case - 2 items removed successfully",
- itemIds[0], itemIds[2]);
-
- DBWriter.removeQueueItem(context, false).get(TIMEOUT, TimeUnit.SECONDS);
- assertQueueByItemIds("Boundary case - no items supplied. queue should see no change",
- itemIds[0], itemIds[2]);
-
- DBWriter.removeQueueItem(context, false,
- itemIds[0], itemIds[4], -1L).get(TIMEOUT, TimeUnit.SECONDS);
- assertQueueByItemIds("Boundary case - items not in queue ignored",
- itemIds[2]);
-
- DBWriter.removeQueueItem(context, false,
- itemIds[2], -1L).get(TIMEOUT, TimeUnit.SECONDS);
- assertQueueByItemIds("Boundary case - invalid itemIds ignored"); // the queue is empty
-
- }
-
- @Test
- public void testMoveQueueItem() throws InterruptedException, ExecutionException, TimeoutException {
- final int NUM_ITEMS = 10;
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < NUM_ITEMS; i++) {
- FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- }
- for (int from = 0; from < NUM_ITEMS; from++) {
- for (int to = 0; to < NUM_ITEMS; to++) {
- if (from == to) {
- continue;
- }
- Log.d(TAG, String.format(Locale.US, "testMoveQueueItem: From=%d, To=%d", from, to));
- final long fromID = feed.getItems().get(from).getId();
-
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setQueue(feed.getItems());
- adapter.close();
-
- DBWriter.moveQueueItem(from, to, false).get(TIMEOUT, TimeUnit.SECONDS);
- adapter = PodDBAdapter.getInstance();
- adapter.open();
- Cursor queue = adapter.getQueueIDCursor();
- assertEquals(NUM_ITEMS, queue.getCount());
- assertTrue(queue.moveToPosition(from));
- assertNotEquals(fromID, queue.getLong(0));
- assertTrue(queue.moveToPosition(to));
- assertEquals(fromID, queue.getLong(0));
-
- queue.close();
- adapter.close();
- }
- }
- }
-
- @Test
- public void testMarkFeedRead() throws InterruptedException, ExecutionException, TimeoutException {
- final int NUM_ITEMS = 10;
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < NUM_ITEMS; i++) {
- FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), FeedItem.UNPLAYED, feed);
- feed.getItems().add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- }
-
- DBWriter.markFeedRead(feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
- List<FeedItem> loadedItems = DBReader.getFeedItemList(feed);
- for (FeedItem item : loadedItems) {
- assertTrue(item.isPlayed());
- }
- }
-
- @Test
- public void testMarkAllItemsReadSameFeed() throws InterruptedException, ExecutionException, TimeoutException {
- final int NUM_ITEMS = 10;
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < NUM_ITEMS; i++) {
- FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), FeedItem.UNPLAYED, feed);
- feed.getItems().add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- }
-
- DBWriter.markAllItemsRead().get(TIMEOUT, TimeUnit.SECONDS);
- List<FeedItem> loadedItems = DBReader.getFeedItemList(feed);
- for (FeedItem item : loadedItems) {
- assertTrue(item.isPlayed());
- }
- }
-
- private static Feed createTestFeed(int numItems) {
- Feed feed = new Feed("url", null, "title");
- feed.setItems(new ArrayList<>());
- for (int i = 0; i < numItems; i++) {
- FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), FeedItem.PLAYED, feed);
- feed.getItems().add(item);
- }
-
- withPodDB(adapter -> adapter.setCompleteFeed(feed));
-
- for (FeedItem item : feed.getItems()) {
- assertTrue(item.getId() != 0);
- }
- return feed;
- }
-
- private static void withPodDB(Consumer<PodDBAdapter> action) {
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- try {
- adapter.open();
- action.accept(adapter);
- } finally {
- adapter.close();
- }
- }
-
- private static void assertQueueByItemIds(
- String message,
- long... itemIdsExpected
- ) {
- List<FeedItem> queue = DBReader.getQueue();
- List<Long> itemIdsActualList = toItemIds(queue);
- List<Long> itemIdsExpectedList = new ArrayList<>(itemIdsExpected.length);
- for (long id : itemIdsExpected) {
- itemIdsExpectedList.add(id);
- }
-
- assertEquals(message, itemIdsExpectedList, itemIdsActualList);
- }
-
- private static List<Long> toItemIds(List<FeedItem> items) {
- List<Long> itemIds = new ArrayList<>(items.size());
- for(FeedItem item : items) {
- itemIds.add(item.getId());
- }
- return itemIds;
- }
-
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
index be1ed6cc2..417a78f02 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java
@@ -2,16 +2,14 @@ package de.test.antennapod.ui;
import android.app.Activity;
import android.content.Intent;
-import androidx.test.platform.app.InstrumentationRegistry;
+
import androidx.test.espresso.Espresso;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
import com.robotium.solo.Solo;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.MainActivity;
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.storage.PodDBAdapter;
-import de.test.antennapod.EspressoTestUtils;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -20,6 +18,12 @@ import org.junit.runner.RunWith;
import java.io.IOException;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.activity.MainActivity;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
+import de.test.antennapod.EspressoTestUtils;
+
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.replaceText;
@@ -28,18 +32,17 @@ import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.contrib.ActivityResultMatchers.hasResultCode;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
-import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static de.test.antennapod.EspressoTestUtils.clickPreference;
import static de.test.antennapod.EspressoTestUtils.openNavDrawer;
-import static de.test.antennapod.EspressoTestUtils.waitForView;
-import static junit.framework.TestCase.assertTrue;
+import static de.test.antennapod.EspressoTestUtils.waitForViewGlobally;
import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
/**
- * User interface tests for MainActivity
+ * User interface tests for MainActivity.
*/
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
@@ -48,19 +51,19 @@ public class MainActivityTest {
private UITestUtils uiTestUtils;
@Rule
- public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(MainActivity.class, false, false);
+ public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(MainActivity.class, false, false);
@Before
public void setUp() throws IOException {
EspressoTestUtils.clearPreferences();
EspressoTestUtils.clearDatabase();
- mActivityRule.launchActivity(new Intent());
+ activityRule.launchActivity(new Intent());
uiTestUtils = new UITestUtils(InstrumentationRegistry.getInstrumentation().getTargetContext());
uiTestUtils.setup();
- solo = new Solo(InstrumentationRegistry.getInstrumentation(), mActivityRule.getActivity());
+ solo = new Solo(InstrumentationRegistry.getInstrumentation(), activityRule.getActivity());
}
@After
@@ -71,16 +74,22 @@ public class MainActivityTest {
@Test
public void testAddFeed() throws Exception {
+ // connect to podcast feed
uiTestUtils.addHostedFeedData();
final Feed feed = uiTestUtils.hostedFeeds.get(0);
openNavDrawer();
onView(withText(R.string.add_feed_label)).perform(click());
- onView(withId(R.id.btn_add_via_url)).perform(scrollTo(), click());
- onView(withId(R.id.text)).perform(replaceText(feed.getDownload_url()));
+ onView(withId(R.id.addViaUrlButton)).perform(scrollTo(), click());
+ onView(withId(R.id.urlEditText)).perform(replaceText(feed.getDownload_url()));
onView(withText(R.string.confirm_label)).perform(scrollTo(), click());
+
+ // subscribe podcast
Espresso.closeSoftKeyboard();
+ waitForViewGlobally(withText(R.string.subscribe_label), 15000);
onView(withText(R.string.subscribe_label)).perform(click());
- onView(isRoot()).perform(waitForView(withId(R.id.butShowSettings), 5000));
+
+ // wait for podcast feed item list
+ waitForViewGlobally(withId(R.id.butShowSettings), 15000);
}
@Test
@@ -100,7 +109,7 @@ public class MainActivityTest {
onView(allOf(withId(R.id.toolbar), isDisplayed())).check(
matches(hasDescendant(withText(R.string.subscriptions_label))));
solo.goBack();
- assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
+ assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
}
@Test
@@ -113,7 +122,7 @@ public class MainActivityTest {
solo.goBackToActivity(MainActivity.class.getSimpleName());
solo.goBack();
solo.goBack();
- assertTrue(((MainActivity)solo.getCurrentActivity()).isDrawerOpen());
+ assertTrue(((MainActivity) solo.getCurrentActivity()).isDrawerOpen());
}
@Test
@@ -127,7 +136,7 @@ public class MainActivityTest {
solo.goBack();
solo.goBack();
solo.goBack();
- assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
+ assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
}
@Test
@@ -142,7 +151,7 @@ public class MainActivityTest {
solo.goBack();
onView(withText(R.string.yes)).perform(click());
Thread.sleep(100);
- assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
+ assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
}
@Test
@@ -155,6 +164,6 @@ public class MainActivityTest {
solo.goBackToActivity(MainActivity.class.getSimpleName());
solo.goBack();
solo.goBack();
- assertThat(mActivityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
+ assertThat(activityRule.getActivityResult(), hasResultCode(Activity.RESULT_CANCELED));
}
}
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java
index ade5ea298..53396372a 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java
@@ -42,9 +42,9 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static de.test.antennapod.EspressoTestUtils.onDrawerItem;
import static de.test.antennapod.EspressoTestUtils.waitForView;
import static de.test.antennapod.NthMatcher.first;
-import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.Matchers.allOf;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
/**
* User interface tests for MainActivity drawer.
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 9bf89980c..bba546a88 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java
@@ -15,6 +15,7 @@ import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.ExceptFavoriteCleanupAlgorithm;
import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.danoeh.antennapod.fragment.QueueFragment;
import de.danoeh.antennapod.fragment.SubscriptionFragment;
@@ -43,9 +44,9 @@ import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static de.test.antennapod.EspressoTestUtils.clickPreference;
import static de.test.antennapod.EspressoTestUtils.waitForView;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
-import static junit.framework.TestCase.assertTrue;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertTrue;
@LargeTest
public class PreferencesTest {
@@ -372,6 +373,17 @@ public class PreferencesTest {
}
@Test
+ public void testEpisodeCleanupFavoriteOnly() {
+ clickPreference(R.string.network_pref);
+ onView(withText(R.string.pref_automatic_download_title)).perform(click());
+ onView(withText(R.string.pref_episode_cleanup_title)).perform(click());
+ onView(isRoot()).perform(waitForView(withText(R.string.episode_cleanup_except_favorite_removal), 1000));
+ onView(withText(R.string.episode_cleanup_except_favorite_removal)).perform(click());
+ Awaitility.await().atMost(1000, MILLISECONDS)
+ .until(() -> UserPreferences.getEpisodeCleanupAlgorithm() instanceof ExceptFavoriteCleanupAlgorithm);
+ }
+
+ @Test
public void testEpisodeCleanupQueueOnly() {
clickPreference(R.string.network_pref);
onView(withText(R.string.pref_automatic_download_title)).perform(click());
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java b/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java
index 634904f71..5b291752d 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java
@@ -1,15 +1,12 @@
package de.test.antennapod.ui;
import android.content.Intent;
-import android.view.View;
-import androidx.test.espresso.Espresso;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.fragment.QueueFragment;
import de.test.antennapod.EspressoTestUtils;
-import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java
index 5f79e935c..739efcfd2 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java
@@ -14,8 +14,8 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
-import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.QueueFragment;
+import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.test.antennapod.EspressoTestUtils;
import de.test.antennapod.IgnoreOnCi;
import org.awaitility.Awaitility;
@@ -37,8 +37,6 @@ import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static de.test.antennapod.EspressoTestUtils.waitForView;
-import static de.test.antennapod.NthMatcher.first;
-import static org.hamcrest.Matchers.allOf;
/**
* User interface tests for changing the playback speed.
@@ -74,7 +72,7 @@ public class SpeedChangeTest {
UserPreferences.setPlaybackSpeedArray(Arrays.asList(1.0f, 2.0f, 3.0f));
EspressoTestUtils.tryKillPlaybackService();
- activityRule.launchActivity(new Intent().putExtra(MainActivity.EXTRA_OPEN_PLAYER, true));
+ activityRule.launchActivity(new Intent().putExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, true));
controller = new PlaybackController(activityRule.getActivity());
controller.init();
controller.getMedia(); // To load media
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java
index 60516454f..54592df0b 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtilsTest.java
@@ -6,7 +6,6 @@ import java.net.URL;
import java.util.List;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
import androidx.test.filters.MediumTest;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
diff --git a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java b/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java
deleted file mode 100644
index 93e5bcb74..000000000
--- a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package de.test.antennapod.util;
-
-import android.content.Context;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import android.text.TextUtils;
-
-import java.io.File;
-import java.io.IOException;
-
-import de.danoeh.antennapod.core.util.FileNameGenerator;
-import org.apache.commons.lang3.StringUtils;
-import org.junit.After;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-@SmallTest
-public class FilenameGeneratorTest {
-
- public FilenameGeneratorTest() {
- super();
- }
-
- @Test
- public void testGenerateFileName() throws IOException {
- String result = FileNameGenerator.generateFileName("abc abc");
- assertEquals(result, "abc abc");
- createFiles(result);
- }
-
- @Test
- public void testGenerateFileName1() throws IOException {
- String result = FileNameGenerator.generateFileName("ab/c: <abc");
- assertEquals(result, "abc abc");
- createFiles(result);
- }
-
- @Test
- public void testGenerateFileName2() throws IOException {
- String result = FileNameGenerator.generateFileName("abc abc ");
- assertEquals(result, "abc abc");
- createFiles(result);
- }
-
- @Test
- public void testFeedTitleContainsApostrophe() {
- String result = FileNameGenerator.generateFileName("Feed's Title ...");
- assertEquals("Feeds Title", result);
- }
-
- @Test
- public void testFeedTitleContainsDash() {
- String result = FileNameGenerator.generateFileName("Left - Right");
- assertEquals("Left - Right", result);
- }
-
- @Test
- public void testFeedTitleContainsAccents() {
- String result = FileNameGenerator.generateFileName("Äàáâãå");
- assertEquals("Aaaaaa", result);
- }
-
- @Test
- public void testInvalidInput() {
- String result = FileNameGenerator.generateFileName("???");
- assertFalse(TextUtils.isEmpty(result));
- }
-
- @Test
- public void testLongFilename() throws IOException {
- String longName = StringUtils.repeat("x", 20 + FileNameGenerator.MAX_FILENAME_LENGTH);
- String result = FileNameGenerator.generateFileName(longName);
- assertTrue(result.length() <= FileNameGenerator.MAX_FILENAME_LENGTH);
- createFiles(result);
- }
-
- @Test
- public void testLongFilenameNotEquals() {
- // Verify that the name is not just trimmed and different suffixes end up with the same name
- String longName = StringUtils.repeat("x", 20 + FileNameGenerator.MAX_FILENAME_LENGTH);
- String result1 = FileNameGenerator.generateFileName(longName + "a");
- String result2 = FileNameGenerator.generateFileName(longName + "b");
- assertNotEquals(result1, result2);
- }
-
- /**
- * Tests if files can be created.
- *
- * @throws IOException
- */
- private void createFiles(String name) throws IOException {
- File cache = InstrumentationRegistry.getInstrumentation().getTargetContext().getExternalCacheDir();
- File testFile = new File(cache, name);
- testFile.mkdir();
- assertTrue(testFile.exists());
- testFile.delete();
- assertTrue(testFile.createNewFile());
- }
-
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java b/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java
deleted file mode 100644
index 7f26ff612..000000000
--- a/app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java
+++ /dev/null
@@ -1,157 +0,0 @@
-package de.test.antennapod.util;
-
-import androidx.test.filters.SmallTest;
-import de.danoeh.antennapod.core.util.URLChecker;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class for URLChecker
- */
-@SmallTest
-public class URLCheckerTest {
-
- @Test
- public void testCorrectURLHttp() {
- final String in = "http://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals(in, out);
- }
-
- @Test
- public void testCorrectURLHttps() {
- final String in = "https://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals(in, out);
- }
-
- @Test
- public void testMissingProtocol() {
- final String in = "example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals("http://example.com", out);
- }
-
- @Test
- public void testFeedProtocol() {
- final String in = "feed://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals("http://example.com", out);
- }
-
- @Test
- public void testPcastProtocolNoScheme() {
- final String in = "pcast://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals("http://example.com", out);
- }
-
- @Test
- public void testItpcProtocol() {
- final String in = "itpc://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals("http://example.com", out);
- }
-
- @Test
- public void testItpcProtocolWithScheme() {
- final String in = "itpc://https://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals("https://example.com", out);
- }
-
- @Test
- public void testWhiteSpaceUrlShouldNotAppend() {
- final String in = "\n http://example.com \t";
- final String out = URLChecker.prepareURL(in);
- assertEquals("http://example.com", out);
- }
-
- @Test
- public void testWhiteSpaceShouldAppend() {
- final String in = "\n example.com \t";
- final String out = URLChecker.prepareURL(in);
- assertEquals("http://example.com", out);
- }
-
- @Test
- public void testAntennaPodSubscribeProtocolNoScheme() throws Exception {
- final String in = "antennapod-subscribe://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals("http://example.com", out);
- }
-
- @Test
- public void testPcastProtocolWithScheme() {
- final String in = "pcast://https://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals("https://example.com", out);
- }
-
- @Test
- public void testAntennaPodSubscribeProtocolWithScheme() throws Exception {
- final String in = "antennapod-subscribe://https://example.com";
- final String out = URLChecker.prepareURL(in);
- assertEquals("https://example.com", out);
- }
-
- @Test
- public void testProtocolRelativeUrlIsAbsolute() throws Exception {
- final String in = "https://example.com";
- final String inBase = "http://examplebase.com";
- final String out = URLChecker.prepareURL(in, inBase);
- assertEquals(in, out);
- }
-
- @Test
- public void testProtocolRelativeUrlIsRelativeHttps() throws Exception {
- final String in = "//example.com";
- final String inBase = "https://examplebase.com";
- final String out = URLChecker.prepareURL(in, inBase);
- assertEquals("https://example.com", out);
- }
-
- @Test
- public void testProtocolRelativeUrlIsHttpsWithAPSubscribeProtocol() throws Exception {
- final String in = "//example.com";
- final String inBase = "antennapod-subscribe://https://examplebase.com";
- final String out = URLChecker.prepareURL(in, inBase);
- assertEquals("https://example.com", out);
- }
-
- @Test
- public void testProtocolRelativeUrlBaseUrlNull() throws Exception {
- final String in = "example.com";
- final String out = URLChecker.prepareURL(in, null);
- assertEquals("http://example.com", out);
- }
-
- @Test
- public void testUrlEqualsSame() {
- assertTrue(URLChecker.urlEquals("https://www.example.com/test", "https://www.example.com/test"));
- assertTrue(URLChecker.urlEquals("https://www.example.com/test", "https://www.example.com/test/"));
- assertTrue(URLChecker.urlEquals("https://www.example.com/test", "https://www.example.com//test"));
- assertTrue(URLChecker.urlEquals("https://www.example.com", "https://www.example.com/"));
- assertTrue(URLChecker.urlEquals("https://www.example.com", "http://www.example.com"));
- assertTrue(URLChecker.urlEquals("http://www.example.com/", "https://www.example.com/"));
- assertTrue(URLChecker.urlEquals("https://www.example.com/?id=42", "https://www.example.com/?id=42"));
- assertTrue(URLChecker.urlEquals("https://example.com/podcast%20test", "https://example.com/podcast test"));
- assertTrue(URLChecker.urlEquals("https://example.com/?a=podcast%20test", "https://example.com/?a=podcast test"));
- assertTrue(URLChecker.urlEquals("https://example.com/?", "https://example.com/"));
- assertTrue(URLChecker.urlEquals("https://example.com/?", "https://example.com"));
- assertTrue(URLChecker.urlEquals("https://Example.com", "https://example.com"));
- assertTrue(URLChecker.urlEquals("https://example.com/test", "https://example.com/Test"));
- }
-
- @Test
- public void testUrlEqualsDifferent() {
- assertFalse(URLChecker.urlEquals("https://www.example.com/test", "https://www.example2.com/test"));
- assertFalse(URLChecker.urlEquals("https://www.example.com/test", "https://www.example.de/test"));
- assertFalse(URLChecker.urlEquals("https://example.com/", "https://otherpodcast.example.com/"));
- assertFalse(URLChecker.urlEquals("https://www.example.com/?id=42&a=b", "https://www.example.com/?id=43&a=b"));
- assertFalse(URLChecker.urlEquals("https://example.com/podcast%25test", "https://example.com/podcast test"));
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java b/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java
deleted file mode 100644
index ed37b7daa..000000000
--- a/app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-package de.test.antennapod.util.playback;
-
-import android.content.Context;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Document;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Elements;
-
-import java.util.Date;
-import java.util.List;
-
-import de.danoeh.antennapod.core.feed.Chapter;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.util.playback.Playable;
-import de.danoeh.antennapod.core.util.playback.Timeline;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class for timeline.
- */
-@SmallTest
-public class TimelineTest {
-
- private Context context;
-
- @Before
- public void setUp() {
- context = InstrumentationRegistry.getInstrumentation().getTargetContext();
- }
-
- private Playable newTestPlayable(List<Chapter> chapters, String shownotes, int duration) {
- FeedItem item = new FeedItem(0, "Item", "item-id", "http://example.com/item", new Date(), FeedItem.PLAYED, null);
- item.setChapters(chapters);
- item.setContentEncoded(shownotes);
- FeedMedia media = new FeedMedia(item, "http://example.com/episode", 100, "audio/mp3");
- media.setDuration(duration);
- item.setMedia(media);
- return media;
- }
-
- @Test
- public void testProcessShownotesAddTimecodeHHMMSSNoChapters() {
- final String timeStr = "10:11:12";
- final long time = 3600 * 1000 * 10 + 60 * 1000 * 11 + 12 * 1000;
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode "
- + timeStr + " here.</p>", Integer.MAX_VALUE);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAddTimecodeHHMMSSMoreThen24HoursNoChapters() {
- final String timeStr = "25:00:00";
- final long time = 25 * 60 * 60 * 1000;
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode "
- + timeStr + " here.</p>", Integer.MAX_VALUE);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAddTimecodeHHMMNoChapters() {
- final String timeStr = "10:11";
- final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode "
- + timeStr + " here.</p>", Integer.MAX_VALUE);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAddTimecodeMMSSNoChapters() {
- final String timeStr = "10:11";
- final long time = 10 * 60 * 1000 + 11 * 1000;
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode "
- + timeStr + " here.</p>", 11 * 60 * 1000);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAddTimecodeHMMSSNoChapters() {
- final String timeStr = "2:11:12";
- final long time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000;
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode "
- + timeStr + " here.</p>", Integer.MAX_VALUE);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAddTimecodeMSSNoChapters() {
- final String timeStr = "1:12";
- final long time = 60 * 1000 + 12 * 1000;
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode "
- + timeStr + " here.</p>", 2 * 60 * 1000);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAddNoTimecodeDuration() {
- final String timeStr = "2:11:12";
- final int time = 2 * 60 * 60 * 1000 + 11 * 60 * 1000 + 12 * 1000;
- String originalText = "<p> Some test text with a timecode " + timeStr + " here.</p>";
- Playable p = newTestPlayable(null, originalText, time);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- Document d = Jsoup.parse(res);
- assertEquals("Should not parse time codes that equal duration", 0, d.body().getElementsByTag("a").size());
- }
-
- @Test
- public void testProcessShownotesAddTimecodeMultipleFormatsNoChapters() {
- final String[] timeStrings = new String[]{ "10:12", "1:10:12" };
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode "
- + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 2 * 60 * 60 * 1000);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000,
- 60 * 60 * 1000 + 10 * 60 * 1000 + 12 * 1000 }, timeStrings);
- }
-
- @Test
- public void testProcessShownotesAddTimecodeMultipleShortFormatNoChapters() {
-
- // One of these timecodes fits as HH:MM and one does not so both should be parsed as MM:SS.
- final String[] timeStrings = new String[]{ "10:12", "2:12" };
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode "
- + timeStrings[0] + " here. Hey look another one " + timeStrings[1] + " here!</p>", 3 * 60 * 60 * 1000);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{ 10 * 60 * 1000 + 12 * 1000, 2 * 60 * 1000 + 12 * 1000 }, timeStrings);
- }
-
- @Test
- public void testProcessShownotesAddTimecodeParentheses() {
- final String timeStr = "10:11";
- final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode ("
- + timeStr + ") here.</p>", Integer.MAX_VALUE);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAddTimecodeBrackets() {
- final String timeStr = "10:11";
- final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode ["
- + timeStr + "] here.</p>", Integer.MAX_VALUE);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAddTimecodeAngleBrackets() {
- final String timeStr = "10:11";
- final long time = 3600 * 1000 * 10 + 60 * 1000 * 11;
-
- Playable p = newTestPlayable(null, "<p> Some test text with a timecode <"
- + timeStr + "> here.</p>", Integer.MAX_VALUE);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[]{time}, new String[]{timeStr});
- }
-
- @Test
- public void testProcessShownotesAndInvalidTimecode() {
- final String[] timeStrs = new String[] {"2:1", "0:0", "000", "00", "00:000"};
-
- StringBuilder shownotes = new StringBuilder("<p> Some test text with timecodes ");
- for (String timeStr : timeStrs) {
- shownotes.append(timeStr).append(" ");
- }
- shownotes.append("here.</p>");
-
- Playable p = newTestPlayable(null, shownotes.toString(), Integer.MAX_VALUE);
- Timeline t = new Timeline(context, p);
- String res = t.processShownotes();
- checkLinkCorrect(res, new long[0], new String[0]);
- }
-
- private void checkLinkCorrect(String res, long[] timecodes, String[] timecodeStr) {
- assertNotNull(res);
- Document d = Jsoup.parse(res);
- Elements links = d.body().getElementsByTag("a");
- int countedLinks = 0;
- for (Element link : links) {
- String href = link.attributes().get("href");
- String text = link.text();
- if (href.startsWith("antennapod://")) {
- assertTrue(href.endsWith(String.valueOf(timecodes[countedLinks])));
- assertEquals(timecodeStr[countedLinks], text);
- countedLinks++;
- assertTrue("Contains too many links: " + countedLinks + " > "
- + timecodes.length, countedLinks <= timecodes.length);
- }
- }
- assertEquals(timecodes.length, countedLinks);
- }
-
- @Test
- public void testIsTimecodeLink() {
- assertFalse(Timeline.isTimecodeLink(null));
- assertFalse(Timeline.isTimecodeLink("http://antennapod/timecode/123123"));
- assertFalse(Timeline.isTimecodeLink("antennapod://timecode/"));
- assertFalse(Timeline.isTimecodeLink("antennapod://123123"));
- assertFalse(Timeline.isTimecodeLink("antennapod://timecode/123123a"));
- assertTrue(Timeline.isTimecodeLink("antennapod://timecode/123"));
- assertTrue(Timeline.isTimecodeLink("antennapod://timecode/1"));
- }
-
- @Test
- public void testGetTimecodeLinkTime() {
- assertEquals(-1, Timeline.getTimecodeLinkTime(null));
- assertEquals(-1, Timeline.getTimecodeLinkTime("http://timecode/123"));
- assertEquals(123, Timeline.getTimecodeLinkTime("antennapod://timecode/123"));
-
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java b/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java
deleted file mode 100644
index b213a5efa..000000000
--- a/app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package de.test.antennapod.util.syndication;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.nio.charset.Charset;
-import java.util.Map;
-
-import de.danoeh.antennapod.core.util.syndication.FeedDiscoverer;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Test class for FeedDiscoverer
- */
-public class FeedDiscovererTest {
-
- private FeedDiscoverer fd;
-
- private File testDir;
-
- @Before
- public void setUp() throws Exception {
- fd = new FeedDiscoverer();
- testDir = new File(InstrumentationRegistry
- .getInstrumentation().getTargetContext().getFilesDir(), "FeedDiscovererTest");
- testDir.mkdir();
- assertTrue(testDir.exists());
- }
-
- @After
- public void tearDown() throws Exception {
- FileUtils.deleteDirectory(testDir);
- }
-
- private String createTestHtmlString(String rel, String type, String href, String title) {
- return String.format("<html><head><title>Test</title><link rel=\"%s\" type=\"%s\" href=\"%s\" title=\"%s\"></head><body></body></html>",
- rel, type, href, title);
- }
-
- private String createTestHtmlString(String rel, String type, String href) {
- return String.format("<html><head><title>Test</title><link rel=\"%s\" type=\"%s\" href=\"%s\"></head><body></body></html>",
- rel, type, href);
- }
-
- private void checkFindUrls(boolean isAlternate, boolean isRss, boolean withTitle, boolean isAbsolute, boolean fromString) throws Exception {
- final String title = "Test title";
- final String hrefAbs = "http://example.com/feed";
- final String hrefRel = "/feed";
- final String base = "http://example.com";
-
- final String rel = (isAlternate) ? "alternate" : "feed";
- final String type = (isRss) ? "application/rss+xml" : "application/atom+xml";
- final String href = (isAbsolute) ? hrefAbs : hrefRel;
-
- Map<String, String> res;
- String html = (withTitle) ? createTestHtmlString(rel, type, href, title)
- : createTestHtmlString(rel, type, href);
- if (fromString) {
- res = fd.findLinks(html, base);
- } else {
- File testFile = new File(testDir, "feed");
- FileOutputStream out = new FileOutputStream(testFile);
- IOUtils.write(html, out, Charset.forName("UTF-8"));
- out.close();
- res = fd.findLinks(testFile, base);
- }
-
- assertNotNull(res);
- assertEquals(1, res.size());
- for (String key : res.keySet()) {
- assertEquals(hrefAbs, key);
- }
- assertTrue(res.containsKey(hrefAbs));
- if (withTitle) {
- assertEquals(title, res.get(hrefAbs));
- } else {
- assertEquals(href, res.get(hrefAbs));
- }
- }
-
- @Test
- public void testAlternateRSSWithTitleAbsolute() throws Exception {
- checkFindUrls(true, true, true, true, true);
- }
-
- @Test
- public void testAlternateRSSWithTitleRelative() throws Exception {
- checkFindUrls(true, true, true, false, true);
- }
-
- @Test
- public void testAlternateRSSNoTitleAbsolute() throws Exception {
- checkFindUrls(true, true, false, true, true);
- }
-
- @Test
- public void testAlternateRSSNoTitleRelative() throws Exception {
- checkFindUrls(true, true, false, false, true);
- }
-
- @Test
- public void testAlternateAtomWithTitleAbsolute() throws Exception {
- checkFindUrls(true, false, true, true, true);
- }
-
- @Test
- public void testFeedAtomWithTitleAbsolute() throws Exception {
- checkFindUrls(false, false, true, true, true);
- }
-
- @Test
- public void testAlternateRSSWithTitleAbsoluteFromFile() throws Exception {
- checkFindUrls(true, true, true, true, false);
- }
-}
diff --git a/app/src/androidTest/java/de/test/antennapod/util/syndication/feedgenerator/AtomGenerator.java b/app/src/androidTest/java/de/test/antennapod/util/syndication/feedgenerator/AtomGenerator.java
deleted file mode 100644
index c80e3bbb1..000000000
--- a/app/src/androidTest/java/de/test/antennapod/util/syndication/feedgenerator/AtomGenerator.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package de.test.antennapod.util.syndication.feedgenerator;
-
-import android.util.Xml;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedItem;
-import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.util.DateUtils;
-
-/**
- * Creates Atom feeds. See FeedGenerator for more information.
- */
-public class AtomGenerator implements FeedGenerator {
-
- private static final String NS_ATOM = "http://www.w3.org/2005/Atom";
-
- private static final long FEATURE_USE_RFC3339LOCAL = 1;
-
- @Override
- public void writeFeed(Feed feed, OutputStream outputStream, String encoding, long flags) throws IOException {
- if (feed == null) throw new IllegalArgumentException("feed = null");
- if (outputStream == null) throw new IllegalArgumentException("outputStream = null");
- if (encoding == null) throw new IllegalArgumentException("encoding = null");
-
- XmlSerializer xml = Xml.newSerializer();
- xml.setOutput(outputStream, encoding);
- xml.startDocument(encoding, null);
-
- xml.startTag(null, "feed");
- xml.attribute(null, "xmlns", NS_ATOM);
-
- // Write Feed data
- if (feed.getIdentifyingValue() != null) {
- xml.startTag(null, "id");
- xml.text(feed.getIdentifyingValue());
- xml.endTag(null, "id");
- }
- if (feed.getTitle() != null) {
- xml.startTag(null, "title");
- xml.text(feed.getTitle());
- xml.endTag(null, "title");
- }
- if (feed.getLink() != null) {
- xml.startTag(null, "link");
- xml.attribute(null, "rel", "alternate");
- xml.attribute(null, "href", feed.getLink());
- xml.endTag(null, "link");
- }
- if (feed.getDescription() != null) {
- xml.startTag(null, "subtitle");
- xml.text(feed.getDescription());
- xml.endTag(null, "subtitle");
- }
- if (feed.getImageUrl() != null) {
- xml.startTag(null, "logo");
- xml.text(feed.getImageUrl());
- xml.endTag(null, "logo");
- }
-
- if (feed.getPaymentLink() != null) {
- GeneratorUtil.addPaymentLink(xml, feed.getPaymentLink(), false);
- }
-
- // Write FeedItem data
- if (feed.getItems() != null) {
- for (FeedItem item : feed.getItems()) {
- xml.startTag(null, "entry");
-
- if (item.getIdentifyingValue() != null) {
- xml.startTag(null, "id");
- xml.text(item.getIdentifyingValue());
- xml.endTag(null, "id");
- }
- if (item.getTitle() != null) {
- xml.startTag(null, "title");
- xml.text(item.getTitle());
- xml.endTag(null, "title");
- }
- if (item.getLink() != null) {
- xml.startTag(null, "link");
- xml.attribute(null, "rel", "alternate");
- xml.attribute(null, "href", item.getLink());
- xml.endTag(null, "link");
- }
- if (item.getPubDate() != null) {
- xml.startTag(null, "published");
- if ((flags & FEATURE_USE_RFC3339LOCAL) != 0) {
- xml.text(DateUtils.formatRFC3339Local(item.getPubDate()));
- } else {
- xml.text(DateUtils.formatRFC3339UTC(item.getPubDate()));
- }
- xml.endTag(null, "published");
- }
- if (item.getDescription() != null) {
- xml.startTag(null, "content");
- xml.text(item.getDescription());
- xml.endTag(null, "content");
- }
- if (item.getMedia() != null) {
- FeedMedia media = item.getMedia();
- xml.startTag(null, "link");
- xml.attribute(null, "rel", "enclosure");
- xml.attribute(null, "href", media.getDownload_url());
- xml.attribute(null, "type", media.getMime_type());
- xml.attribute(null, "length", String.valueOf(media.getSize()));
- xml.endTag(null, "link");
- }
-
- if (item.getPaymentLink() != null) {
- GeneratorUtil.addPaymentLink(xml, item.getPaymentLink(), false);
- }
-
- xml.endTag(null, "entry");
- }
- }
-
- writeAdditionalAttributes(xml);
-
- xml.endTag(null, "feed");
- xml.endDocument();
- }
-
- protected void writeAdditionalAttributes(XmlSerializer xml) throws IOException {
-
- }
-}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fb205b1c3..79fcc430f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -84,10 +84,37 @@
android:windowSoftInputMode="stateAlwaysHidden"
android:launchMode="singleTask"
android:label="@string/app_name">
+ <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/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"
+ android:scheme="https" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="de.danoeh.antennapod.intents.MAIN_ACTIVITY" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
</activity>
<activity
android:name=".activity.DownloadAuthenticationActivity"
+ android:theme="@style/Theme.AntennaPod.Dark.Translucent"
android:launchMode="singleInstance"/>
<activity
@@ -190,13 +217,8 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.MainActivity"/>
<intent-filter>
- <action android:name="android.intent.action.VIEW"/>
-
- <category android:name="android.intent.category.DEFAULT"/>
- <category android:name="android.intent.category.BROWSABLE"/>
-
- <data android:scheme="file"/>
- <data android:mimeType="video/*"/>
+ <action android:name="de.danoeh.antennapod.intents.VIDEO_PLAYER" />
+ <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
@@ -292,19 +314,6 @@
</activity>
- <activity
- android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"
- android:configChanges="orientation"
- android:label="@string/gpodnet_auth_label">
- <intent-filter>
- <action android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
- <category android:name="android.intent.category.DEFAULT"/>
- </intent-filter>
- <meta-data
- android:name="android.support.PARENT_ACTIVITY"
- android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
- </activity>
-
<receiver android:name=".receiver.ConnectivityActionReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
@@ -333,6 +342,10 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
+
+ <meta-data
+ android:name="com.google.android.actions"
+ android:resource="@xml/actions" />
</application>
</manifest>
diff --git a/app/src/main/assets/licenses.xml b/app/src/main/assets/licenses.xml
index 4f0255182..aa0ad740b 100644
--- a/app/src/main/assets/licenses.xml
+++ b/app/src/main/assets/licenses.xml
@@ -4,7 +4,7 @@
name="AntennaPod"
author="The AntennaPod team"
website="https://github.com/AntennaPod/AntennaPod/"
- license="MIT"
+ license="GPL-3.0"
licenseText="LICENSE.txt" />
<library
name="AntennaPod-AudioPlayer"
@@ -91,12 +91,6 @@
license="Apache 2.0"
licenseText="LICENSE_APACHE-2.0.txt" />
<library
- name="RecyclerView-FlexibleDivider"
- author="yqritc"
- website="https://github.com/yqritc/RecyclerView-FlexibleDivider"
- license="Apache 2.0"
- licenseText="LICENSE_APACHE-2.0.txt" />
- <library
name="RxAndroid"
author="ReactiveX"
website="https://github.com/ReactiveX/RxAndroid"
diff --git a/app/src/main/assets/special_thanks.csv b/app/src/main/assets/special_thanks.csv
index 348e3208e..ab44bd7f4 100644
--- a/app/src/main/assets/special_thanks.csv
+++ b/app/src/main/assets/special_thanks.csv
@@ -1,3 +1,4 @@
221 Pixels;Logo design;https://avatars2.githubusercontent.com/u/58243143?s=60&v=4
+Anxhelo Lushka;Website design;https://avatars2.githubusercontent.com/u/25004151?s=60&v=4
ByteHamster;Forum admin;https://avatars2.githubusercontent.com/u/5811634?s=60&v=4
Keunes;Communications;https://avatars2.githubusercontent.com/u/11229646?s=60&v=4
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 bff11fa5e..721291597 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/BugReportActivity.java
@@ -11,10 +11,15 @@ import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import com.google.android.material.snackbar.Snackbar;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
+import android.view.Menu;
+import android.view.MenuItem;
import android.widget.TextView;
@@ -67,42 +72,65 @@ public class BugReportActivity extends AppCompatActivity {
clipboard.setPrimaryClip(clip);
Snackbar.make(findViewById(android.R.id.content), R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT).show();
});
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.bug_report_options, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
- findViewById(R.id.btn_export_logcat).setOnClickListener(v -> {
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ if (item.getItemId() == R.id.export_logcat) {
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this);
+ alertBuilder.setMessage(R.string.confirm_export_log_dialog_message);
+ alertBuilder.setPositiveButton(R.string.confirm_label, (dialog, which) -> {
+ exportLog();
+ dialog.dismiss();
+ });
+ alertBuilder.setNegativeButton(R.string.cancel_label, null);
+ alertBuilder.show();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void exportLog() {
+ try {
+ File filename = new File(UserPreferences.getDataFolder(null), "full-logs.txt");
+ filename.createNewFile();
+ String cmd = "logcat -d -f " + filename.getAbsolutePath();
+ Runtime.getRuntime().exec(cmd);
+ //share file
try {
- File filename = new File(UserPreferences.getDataFolder(null), "full-logs.txt");
- filename.createNewFile();
- String cmd = "logcat -d -f " + filename.getAbsolutePath();
- Runtime.getRuntime().exec(cmd);
- //share file
- try {
- Intent i = new Intent(Intent.ACTION_SEND);
- i.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 i = new Intent(Intent.ACTION_SEND);
+ i.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);
}
- String chooserTitle = getString(de.danoeh.antennapod.core.R.string.share_file_label);
- startActivity(Intent.createChooser(i, chooserTitle));
- } catch (Exception e) {
- e.printStackTrace();
- int strResId = R.string.log_file_share_exception;
- Snackbar.make(findViewById(android.R.id.content), strResId, Snackbar.LENGTH_LONG)
- .show();
}
- } catch (IOException e) {
+ String chooserTitle = getString(de.danoeh.antennapod.core.R.string.share_file_label);
+ startActivity(Intent.createChooser(i, chooserTitle));
+ } catch (Exception e) {
e.printStackTrace();
- Snackbar.make(findViewById(android.R.id.content), e.getMessage(), Snackbar.LENGTH_LONG).show();
+ int strResId = R.string.log_file_share_exception;
+ Snackbar.make(findViewById(android.R.id.content), strResId, Snackbar.LENGTH_LONG)
+ .show();
}
- });
+ } catch (IOException e) {
+ e.printStackTrace();
+ Snackbar.make(findViewById(android.R.id.content), e.getMessage(), Snackbar.LENGTH_LONG).show();
+ }
}
+
}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java
index 1d3d9bf11..0f1d38db6 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java
@@ -1,96 +1,76 @@
package de.danoeh.antennapod.activity;
-import android.app.Activity;
-import android.content.Intent;
import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBar;
+import android.text.TextUtils;
import androidx.appcompat.app.AppCompatActivity;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-
-import org.apache.commons.lang3.Validate;
-
import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.feed.FeedPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequester;
+import de.danoeh.antennapod.dialog.AuthenticationDialog;
+import io.reactivex.Completable;
+import io.reactivex.android.schedulers.AndroidSchedulers;
+import io.reactivex.schedulers.Schedulers;
+import org.apache.commons.lang3.Validate;
+
/**
* Shows a username and a password text field.
* The activity MUST be started with the ARG_DOWNlOAD_REQUEST argument set to a non-null value.
- * Other arguments are optional.
- * The activity's result will be the same DownloadRequest with the entered username and password.
*/
public class DownloadAuthenticationActivity extends AppCompatActivity {
/**
- * The download request object that contains information about the resource that requires a username and a password
+ * The download request object that contains information about the resource that requires a username and a password.
*/
public static final String ARG_DOWNLOAD_REQUEST = "request";
- /**
- * True if the request should be sent to the DownloadRequester when this activity is finished, false otherwise.
- * The default value is false.
- */
- public static final String ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL = "send_to_downloadrequester";
-
- private static final String RESULT_REQUEST = "request";
-
- private EditText etxtUsername;
- private EditText etxtPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getNoTitleTheme());
+ setTheme(UserPreferences.getTranslucentTheme());
super.onCreate(savedInstanceState);
- setContentView(R.layout.download_authentication_activity);
- TextView txtvDescription = findViewById(R.id.txtvDescription);
- etxtUsername = findViewById(R.id.etxtUsername);
- etxtPassword = findViewById(R.id.etxtPassword);
- Button butConfirm = findViewById(R.id.butConfirm);
- Button butCancel = findViewById(R.id.butCancel);
-
Validate.isTrue(getIntent().hasExtra(ARG_DOWNLOAD_REQUEST), "Download request missing");
DownloadRequest request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST);
- boolean sendToDownloadRequester = getIntent().getBooleanExtra(ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, false);
-
- String newDescription = txtvDescription.getText() + ":\n\n" + request.getTitle();
- txtvDescription.setText(newDescription);
-
- if (savedInstanceState != null) {
- etxtUsername.setText(savedInstanceState.getString("username"));
- etxtPassword.setText(savedInstanceState.getString("password"));
- }
- butConfirm.setOnClickListener(v -> {
- String username = etxtUsername.getText().toString();
- String password = etxtPassword.getText().toString();
- request.setUsername(username);
- request.setPassword(password);
- Intent result = new Intent();
- result.putExtra(RESULT_REQUEST, request);
- setResult(Activity.RESULT_OK, result);
-
- if (sendToDownloadRequester) {
- DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request);
+ new AuthenticationDialog(this, R.string.authentication_label, true, "", "") {
+ @Override
+ protected void onConfirmed(String username, String password) {
+ Completable.fromAction(
+ () -> {
+ request.setUsername(username);
+ request.setPassword(password);
+
+ if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ long mediaId = request.getFeedfileId();
+ FeedMedia media = DBReader.getFeedMedia(mediaId);
+ if (media != null) {
+ FeedPreferences preferences = media.getItem().getFeed().getPreferences();
+ if (TextUtils.isEmpty(preferences.getPassword())
+ || TextUtils.isEmpty(preferences.getUsername())) {
+ preferences.setUsername(username);
+ preferences.setPassword(password);
+ DBWriter.setFeedPreferences(preferences);
+ }
+ }
+ }
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> {
+ DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request);
+ finish();
+ });
}
- finish();
- });
-
- butCancel.setOnClickListener(v -> {
- setResult(Activity.RESULT_CANCELED);
- finish();
- });
- }
-
- @Override
- protected void onSaveInstanceState(@NonNull Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putString("username", etxtUsername.getText().toString());
- outState.putString("password", etxtPassword.getText().toString());
+ @Override
+ protected void onCancelled() {
+ finish();
+ }
+ }.show();
}
}
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 d1716e009..69f5fb264 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java
@@ -6,6 +6,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.media.AudioManager;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -40,7 +41,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.StorageUtils;
-import de.danoeh.antennapod.core.util.ThemeUtils;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import de.danoeh.antennapod.dialog.RatingDialog;
import de.danoeh.antennapod.fragment.AddFeedFragment;
@@ -51,9 +52,11 @@ import de.danoeh.antennapod.fragment.FeedItemlistFragment;
import de.danoeh.antennapod.fragment.NavDrawerFragment;
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
import de.danoeh.antennapod.fragment.QueueFragment;
+import de.danoeh.antennapod.fragment.SearchFragment;
import de.danoeh.antennapod.fragment.SubscriptionFragment;
import de.danoeh.antennapod.fragment.TransitionEffect;
import de.danoeh.antennapod.preferences.PreferenceUpgrader;
+import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.danoeh.antennapod.view.LockableBottomSheetBehavior;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
@@ -75,7 +78,6 @@ public class MainActivity extends CastEnabledActivity {
public static final String EXTRA_FRAGMENT_TAG = "fragment_tag";
public static final String EXTRA_FRAGMENT_ARGS = "fragment_args";
public static final String EXTRA_FEED_ID = "fragment_feed_id";
- public static final String EXTRA_OPEN_PLAYER = "open_player";
public static final String EXTRA_REFRESH_ON_START = "refresh_on_start";
public static final String EXTRA_STARTED_FROM_SEARCH = "started_from_search";
public static final String KEY_GENERATED_VIEW_ID = "generated_view_id";
@@ -508,9 +510,11 @@ public class MainActivity extends CastEnabledActivity {
}
}
sheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
- } else if (intent.getBooleanExtra(EXTRA_OPEN_PLAYER, false)) {
+ } else if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_PLAYER, false)) {
sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
bottomSheetCallback.onSlide(null, 1.0f);
+ } else if (Intent.ACTION_VIEW.equals(intent.getAction())) {
+ handleDeeplink(intent.getData());
}
// to avoid handling the intent twice when the configuration changes
setIntent(new Intent(MainActivity.this, MainActivity.class));
@@ -520,6 +524,7 @@ public class MainActivity extends CastEnabledActivity {
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
+ handleNavIntent();
}
public Snackbar showSnackbarAbovePlayer(CharSequence text, int duration) {
@@ -540,6 +545,59 @@ public class MainActivity extends CastEnabledActivity {
return showSnackbarAbovePlayer(getResources().getText(text), duration);
}
+ /**
+ * Handles the deep link incoming via App Actions.
+ * Performs an in-app search or opens the relevant feature of the app
+ * depending on the query.
+ *
+ * @param uri incoming deep link
+ */
+ private void handleDeeplink(Uri uri) {
+ if (uri == null || uri.getPath() == null) {
+ return;
+ }
+ Log.d(TAG, "Handling deeplink: " + uri.toString());
+ switch (uri.getPath()) {
+ case "/deeplink/search":
+ String query = uri.getQueryParameter("query");
+ if (query == null) {
+ return;
+ }
+
+ this.loadChildFragment(SearchFragment.newInstance(query));
+ break;
+ case "/deeplink/main":
+ String feature = uri.getQueryParameter("page");
+ if (feature == null) {
+ return;
+ }
+ switch (feature) {
+ case "DOWNLOADS":
+ loadFragment(DownloadsFragment.TAG, null);
+ break;
+ case "HISTORY":
+ loadFragment(PlaybackHistoryFragment.TAG, null);
+ break;
+ case "EPISODES":
+ loadFragment(EpisodesFragment.TAG, null);
+ break;
+ case "QUEUE":
+ loadFragment(QueueFragment.TAG, null);
+ break;
+ case "SUBSCRIPTIONS":
+ loadFragment(SubscriptionFragment.TAG, null);
+ break;
+ default:
+ showSnackbarAbovePlayer(getString(R.string.app_action_not_found, feature),
+ Snackbar.LENGTH_LONG);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
//Hardware keyboard support
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
@@ -592,5 +650,4 @@ public class MainActivity extends CastEnabledActivity {
}
return super.onKeyUp(keyCode, event);
}
-
}
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
index b03d1e5cd..76359ddc0 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
@@ -1,10 +1,8 @@
package de.danoeh.antennapod.activity;
-import android.Manifest;
import android.annotation.TargetApi;
import android.content.Intent;
import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
import android.graphics.PixelFormat;
import android.os.Build;
import android.os.Bundle;
@@ -17,7 +15,6 @@ import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
-import android.widget.Toast;
import com.bumptech.glide.Glide;
@@ -28,18 +25,16 @@ import org.greenrobot.eventbus.ThreadMode;
import java.text.NumberFormat;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.ActivityCompat;
+import androidx.cardview.widget.CardView;
import androidx.core.app.ActivityOptionsCompat;
-import androidx.core.content.ContextCompat;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
-import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBReader;
@@ -51,11 +46,9 @@ 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.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.MediaPlayerError;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
-import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.danoeh.antennapod.dialog.PlaybackControlsDialog;
import de.danoeh.antennapod.dialog.ShareDialog;
import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
@@ -65,7 +58,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
-
/**
* Provides general features which are both needed for playing audio and video
* files.
@@ -73,9 +65,6 @@ import io.reactivex.schedulers.Schedulers;
public abstract class MediaplayerActivity extends CastEnabledActivity implements OnSeekBarChangeListener {
private static final String TAG = "MediaplayerActivity";
private static final String PREFS = "MediaPlayerActivityPreferences";
- private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
- private static final int REQUEST_CODE_STORAGE_PLAY_VIDEO = 42;
- private static final int REQUEST_CODE_STORAGE_PLAY_AUDIO = 43;
PlaybackController controller;
@@ -88,6 +77,8 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
private ImageButton butFF;
private TextView txtvFF;
private ImageButton butSkip;
+ private CardView cardViewSeek;
+ private TextView txtvSeek;
private boolean showTimeLeft = false;
@@ -473,8 +464,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
if(controller == null || controller.getMedia() == null) {
return false;
}
- SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
- showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
+ showTimeLeft = UserPreferences.shouldShowRemainingTime();
onPositionObserverUpdate();
checkFavorite();
updatePlaybackSpeedButton();
@@ -493,9 +483,11 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
setContentView(getContentViewResourceId());
sbPosition = findViewById(R.id.sbPosition);
txtvPosition = findViewById(R.id.txtvPosition);
+ cardViewSeek = findViewById(R.id.cardViewSeek);
+ txtvSeek = findViewById(R.id.txtvSeek);
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
- showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
+ showTimeLeft = UserPreferences.shouldShowRemainingTime();
Log.d("timeleft", showTimeLeft ? "true" : "false");
txtvLength = findViewById(R.id.txtvLength);
if (txtvLength != null) {
@@ -519,9 +511,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
}
txtvLength.setText(length);
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft);
- editor.apply();
+ UserPreferences.setShowRemainTimeSetting(showTimeLeft);
Log.d("timeleft on click", showTimeLeft ? "true" : "false");
});
}
@@ -619,21 +609,21 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
}
if (fromUser) {
prog = progress / ((float) seekBar.getMax());
- int duration = controller.getDuration();
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
- int position = converter.convert((int) (prog * duration));
- txtvPosition.setText(Converter.getDurationStringLong(position));
-
- if (showTimeLeft) {
- int timeLeft = converter.convert(duration - (int) (prog * duration));
- txtvLength.setText("-" + Converter.getDurationStringLong(timeLeft));
- }
+ int position = converter.convert((int) (prog * controller.getDuration()));
+ txtvSeek.setText(Converter.getDurationStringLong(position));
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
-
+ cardViewSeek.setScaleX(.8f);
+ cardViewSeek.setScaleY(.8f);
+ cardViewSeek.animate()
+ .setInterpolator(new FastOutSlowInInterpolator())
+ .alpha(1f).scaleX(1f).scaleY(1f)
+ .setDuration(200)
+ .start();
}
@Override
@@ -641,6 +631,13 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
if (controller != null) {
controller.seekTo((int) (prog * controller.getDuration()));
}
+ cardViewSeek.setScaleX(1f);
+ cardViewSeek.setScaleY(1f);
+ cardViewSeek.animate()
+ .setInterpolator(new FastOutSlowInInterpolator())
+ .alpha(0f).scaleX(.8f).scaleY(.8f)
+ .setDuration(200)
+ .start();
}
private void checkFavorite() {
@@ -664,50 +661,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
- void playExternalMedia(Intent intent, MediaType type) {
- if (intent == null || intent.getData() == null) {
- return;
- }
- if (Build.VERSION.SDK_INT >= 23
- && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
- != PackageManager.PERMISSION_GRANTED) {
-
- if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
- Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show();
- }
-
- int code = REQUEST_CODE_STORAGE_PLAY_AUDIO;
- if (type == MediaType.VIDEO) {
- code = REQUEST_CODE_STORAGE_PLAY_VIDEO;
- }
- ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, code);
- return;
- }
-
- Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath());
- ExternalMedia media = new ExternalMedia(intent.getData().getPath(), type);
-
- new PlaybackServiceStarter(this, media)
- .callEvenIfRunning(true)
- .startWhenPrepared(true)
- .shouldStream(false)
- .prepareImmediately(true)
- .start();
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) {
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- if (requestCode == REQUEST_CODE_STORAGE_PLAY_AUDIO) {
- playExternalMedia(getIntent(), MediaType.AUDIO);
- } else if (requestCode == REQUEST_CODE_STORAGE_PLAY_VIDEO) {
- playExternalMedia(getIntent(), MediaType.VIDEO);
- }
- } else {
- Toast.makeText(this, R.string.needs_storage_permission, Toast.LENGTH_LONG).show();
- }
- }
-
@Nullable
private static FeedItem getFeedItem(@Nullable Playable playable) {
if (playable instanceof FeedMedia) {
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 3f58e4a92..f53c629b9 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -4,6 +4,7 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.LightingColorFilter;
import android.os.Build;
import android.os.Bundle;
@@ -87,6 +88,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
// Optional argument: specify a title for the actionbar.
private static final int RESULT_ERROR = 2;
private static final String TAG = "OnlineFeedViewActivity";
+ private static final String PREFS = "OnlineFeedViewActivityPreferences";
+ private static final String PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload";
+
private volatile List<Feed> feeds;
private Feed feed;
private String selectedDownloadUrl;
@@ -445,6 +449,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
IntentUtils.sendLocalBroadcast(this, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
});
+ if (UserPreferences.isEnableAutodownload()) {
+ SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
+ viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true));
+ }
+
final int MAX_LINES_COLLAPSED = 10;
description.setMaxLines(MAX_LINES_COLLAPSED);
description.setOnClickListener(v -> {
@@ -509,11 +518,28 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
viewBinding.subscribeButton.setEnabled(true);
viewBinding.subscribeButton.setText(R.string.open_podcast);
if (didPressSubscribe) {
+ didPressSubscribe = false;
+ if (UserPreferences.isEnableAutodownload()) {
+ boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked();
+
+ Feed feed1 = DBReader.getFeed(getFeedId(feed));
+ FeedPreferences feedPreferences = feed1.getPreferences();
+ feedPreferences.setAutoDownload(autoDownload);
+ feed1.savePreferences();
+
+ SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload);
+ editor.apply();
+ }
openFeed();
}
} else {
viewBinding.subscribeButton.setEnabled(true);
viewBinding.subscribeButton.setText(R.string.subscribe_label);
+ if (UserPreferences.isEnableAutodownload()) {
+ viewBinding.autoDownloadCheckBox.setVisibility(View.VISIBLE);
+ }
}
}
}
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 10292b892..d4e9ee5d9 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OpmlImportActivity.java
@@ -17,9 +17,8 @@ import de.danoeh.antennapod.asynctask.OpmlFeedQueuer;
import de.danoeh.antennapod.asynctask.OpmlImportWorker;
import de.danoeh.antennapod.core.export.opml.OpmlElement;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.util.LangUtils;
+
import org.apache.commons.io.ByteOrderMark;
-import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.ArrayUtils;
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java
index d85235cf9..f0c76d545 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/SplashActivity.java
@@ -13,9 +13,9 @@ import android.widget.ProgressBar;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
+import de.danoeh.antennapod.error.CrashReportWriter;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
-import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/**
@@ -44,13 +44,15 @@ public class SplashActivity extends AppCompatActivity {
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
- .subscribe(() -> {
- Intent intent = new Intent(SplashActivity.this, MainActivity.class);
- startActivity(intent);
- overridePendingTransition(0, 0);
- finish();
- }, error -> {
+ .subscribe(
+ () -> {
+ Intent intent = new Intent(SplashActivity.this, MainActivity.class);
+ startActivity(intent);
+ overridePendingTransition(0, 0);
+ finish();
+ }, error -> {
error.printStackTrace();
+ CrashReportWriter.write(error);
Toast.makeText(this, error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
finish();
});
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 9ab1755f5..749681d4a 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java
@@ -15,10 +15,8 @@ import android.view.animation.ScaleAnimation;
import android.widget.EditText;
import android.widget.ImageView;
-import androidx.appcompat.view.menu.ActionMenuItem;
import androidx.core.view.WindowCompat;
import androidx.appcompat.app.ActionBar;
-import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.Menu;
@@ -38,12 +36,12 @@ import java.lang.ref.WeakReference;
import java.util.concurrent.atomic.AtomicBoolean;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.feed.MediaType;
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.util.gui.PictureInPictureUtil;
import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.danoeh.antennapod.view.AspectRatioVideoView;
/**
@@ -89,9 +87,7 @@ public class VideoplayerActivity extends MediaplayerActivity {
@Override
protected void onResume() {
super.onResume();
- if (TextUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
- playExternalMedia(getIntent(), MediaType.VIDEO);
- } else if (PlaybackService.isCasting()) {
+ if (PlaybackService.isCasting()) {
Intent intent = PlaybackService.getPlayerActivityIntent(this);
if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) {
destroyingDueToReload = true;
@@ -348,7 +344,7 @@ public class VideoplayerActivity extends MediaplayerActivity {
Log.d(TAG, "ReloadNotification received, switching to Castplayer now");
destroyingDueToReload = true;
finish();
- startActivity(new Intent(this, MainActivity.class).putExtra(MainActivity.EXTRA_OPEN_PLAYER, true));
+ new MainActivityStarter(this).withOpenPlayer().start();
}
}
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 474b96c38..3020aba43 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java
@@ -2,34 +2,34 @@ package de.danoeh.antennapod.activity;
import android.Manifest;
import android.app.WallpaperManager;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.widget.ImageView;
-import android.widget.RemoteViews;
-import androidx.appcompat.app.AppCompatActivity;
-
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.RelativeLayout;
+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;
-import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
+import de.danoeh.antennapod.core.widget.WidgetUpdaterJobService;
public class WidgetConfigActivity extends AppCompatActivity {
private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
private SeekBar opacitySeekBar;
private TextView opacityTextView;
- private RelativeLayout widgetPreview;
+ private View widgetPreview;
+ private CheckBox ckRewind;
+ private CheckBox ckFastForward;
+ private CheckBox ckSkip;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -74,6 +74,32 @@ public class WidgetConfigActivity extends AppCompatActivity {
}
});
+
+ widgetPreview.findViewById(R.id.txtNoPlaying).setVisibility(View.GONE);
+ TextView title = widgetPreview.findViewById(R.id.txtvTitle);
+ title.setVisibility(View.VISIBLE);
+ title.setText(R.string.app_name);
+ TextView progress = widgetPreview.findViewById(R.id.txtvProgress);
+ progress.setVisibility(View.VISIBLE);
+ progress.setText(R.string.position_default_label);
+
+ ckRewind = findViewById(R.id.ckRewind);
+ ckRewind.setOnClickListener(v -> displayPreviewPanel());
+ ckFastForward = findViewById(R.id.ckFastForward);
+ ckFastForward.setOnClickListener(v -> displayPreviewPanel());
+ ckSkip = findViewById(R.id.ckSkip);
+ ckSkip.setOnClickListener(v -> displayPreviewPanel());
+ }
+
+ private void displayPreviewPanel() {
+ boolean showExtendedPreview = ckRewind.isChecked() || ckFastForward.isChecked() || ckSkip.isChecked();
+ widgetPreview.findViewById(R.id.extendedButtonsContainer)
+ .setVisibility(showExtendedPreview ? View.VISIBLE : View.GONE);
+ widgetPreview.findViewById(R.id.butPlay).setVisibility(showExtendedPreview ? View.GONE : View.VISIBLE);
+ widgetPreview.findViewById(R.id.butFastForward)
+ .setVisibility(ckFastForward.isChecked() ? View.VISIBLE : View.GONE);
+ widgetPreview.findViewById(R.id.butSkip).setVisibility(ckSkip.isChecked() ? View.VISIBLE : View.GONE);
+ widgetPreview.findViewById(R.id.butRew).setVisibility(ckRewind.isChecked() ? View.VISIBLE : View.GONE);
}
private void displayDeviceBackground() {
@@ -92,13 +118,16 @@ public class WidgetConfigActivity extends AppCompatActivity {
SharedPreferences prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor);
+ editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked());
+ editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind.isChecked());
+ editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward.isChecked());
editor.apply();
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
setResult(RESULT_OK, resultValue);
finish();
- PlayerWidgetJobService.updateWidget(this);
+ WidgetUpdaterJobService.performBackgroundUpdate(this);
}
private int getColorWithAlpha(int color, int opacity) {
diff --git a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java
deleted file mode 100644
index cfd6ec702..000000000
--- a/app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java
+++ /dev/null
@@ -1,395 +0,0 @@
-package de.danoeh.antennapod.activity.gpoddernet;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.os.Bundle;
-import androidx.appcompat.app.AppCompatActivity;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.ProgressBar;
-import android.widget.Spinner;
-import android.widget.TextView;
-import android.widget.ViewFlipper;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import de.danoeh.antennapod.BuildConfig;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.MainActivity;
-import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
-import de.danoeh.antennapod.core.sync.SyncService;
-import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
-import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException;
-import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
-
-/**
- * Guides the user through the authentication process
- * Step 1: Request username and password from user
- * Step 2: Choose device from a list of available devices or create a new one
- * Step 3: Choose from a list of actions
- */
-public class GpodnetAuthenticationActivity extends AppCompatActivity {
- private static final String TAG = "GpodnetAuthActivity";
-
- private ViewFlipper viewFlipper;
-
- private static final int STEP_DEFAULT = -1;
- private static final int STEP_LOGIN = 0;
- private static final int STEP_DEVICE = 1;
- private static final int STEP_FINISH = 2;
-
- private int currentStep = -1;
-
- private GpodnetService service;
- private volatile String username;
- private volatile String password;
- private volatile GpodnetDevice selectedDevice;
-
- private View[] views;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- setContentView(R.layout.gpodnetauth_activity);
- service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname());
-
- viewFlipper = findViewById(R.id.viewflipper);
- LayoutInflater inflater = (LayoutInflater)
- getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- views = new View[]{
- inflater.inflate(R.layout.gpodnetauth_credentials, viewFlipper, false),
- inflater.inflate(R.layout.gpodnetauth_device, viewFlipper, false),
- inflater.inflate(R.layout.gpodnetauth_finish, viewFlipper, false)
- };
- for (View view : views) {
- viewFlipper.addView(view);
- }
- advance();
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- finish();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void setupLoginView(View view) {
- final EditText username = view.findViewById(R.id.etxtUsername);
- final EditText password = view.findViewById(R.id.etxtPassword);
- final Button login = view.findViewById(R.id.butLogin);
- final TextView txtvError = view.findViewById(R.id.txtvError);
- final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
-
- password.setOnEditorActionListener((v, actionID, event) ->
- actionID == EditorInfo.IME_ACTION_GO && login.performClick());
-
- login.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
-
- final String usernameStr = username.getText().toString();
- final String passwordStr = password.getText().toString();
-
- if (usernameHasUnwantedChars(usernameStr)) {
- txtvError.setText(R.string.gpodnetsync_username_characters_error);
- txtvError.setVisibility(View.VISIBLE);
- return;
- }
- if (BuildConfig.DEBUG) Log.d(TAG, "Checking login credentials");
- AsyncTask<GpodnetService, Void, Void> authTask = new AsyncTask<GpodnetService, Void, Void>() {
-
- volatile Exception exception;
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- login.setEnabled(false);
- progressBar.setVisibility(View.VISIBLE);
- txtvError.setVisibility(View.GONE);
- // hide the keyboard
- InputMethodManager inputManager = (InputMethodManager)
- getSystemService(Context.INPUT_METHOD_SERVICE);
- inputManager.hideSoftInputFromWindow(login.getWindowToken(),
- InputMethodManager.HIDE_NOT_ALWAYS);
-
- }
-
- @Override
- protected void onPostExecute(Void aVoid) {
- super.onPostExecute(aVoid);
- login.setEnabled(true);
- progressBar.setVisibility(View.GONE);
-
- if (exception == null) {
- advance();
- } else {
- txtvError.setText(exception.getCause().getMessage());
- txtvError.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- protected Void doInBackground(GpodnetService... params) {
- try {
- params[0].authenticate(usernameStr, passwordStr);
- GpodnetAuthenticationActivity.this.username = usernameStr;
- GpodnetAuthenticationActivity.this.password = passwordStr;
- } catch (GpodnetServiceException e) {
- e.printStackTrace();
- exception = e;
- }
- return null;
- }
- };
- authTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, service);
- }
- });
- }
-
- private void setupDeviceView(View view) {
- final EditText deviceID = view.findViewById(R.id.etxtDeviceID);
- final EditText caption = view.findViewById(R.id.etxtCaption);
- final Button createNewDevice = view.findViewById(R.id.butCreateNewDevice);
- final Button chooseDevice = view.findViewById(R.id.butChooseExistingDevice);
- final TextView txtvError = view.findViewById(R.id.txtvError);
- final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);
- final Spinner spinnerDevices = view.findViewById(R.id.spinnerChooseDevice);
-
-
- // load device list
- final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<>();
- new AsyncTask<GpodnetService, Void, List<GpodnetDevice>>() {
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- chooseDevice.setEnabled(false);
- spinnerDevices.setEnabled(false);
- createNewDevice.setEnabled(false);
- }
-
- @Override
- protected void onPostExecute(List<GpodnetDevice> gpodnetDevices) {
- super.onPostExecute(gpodnetDevices);
- if (gpodnetDevices != null) {
- List<String> deviceNames = new ArrayList<>();
- for (GpodnetDevice device : gpodnetDevices) {
- deviceNames.add(device.getCaption());
- }
- spinnerDevices.setAdapter(new ArrayAdapter<>(GpodnetAuthenticationActivity.this,
- android.R.layout.simple_spinner_dropdown_item, deviceNames));
- spinnerDevices.setEnabled(true);
- if (!deviceNames.isEmpty()) {
- chooseDevice.setEnabled(true);
- }
- devices.set(gpodnetDevices);
- deviceID.setText(generateDeviceID(gpodnetDevices));
- createNewDevice.setEnabled(true);
- }
- }
-
- @Override
- protected List<GpodnetDevice> doInBackground(GpodnetService... params) {
- try {
- return params[0].getDevices();
- } catch (GpodnetServiceException e) {
- e.printStackTrace();
- return null;
- }
- }
- }.execute(service);
-
-
- createNewDevice.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (checkDeviceIDText(deviceID, caption, txtvError, devices.get())) {
- final String deviceStr = deviceID.getText().toString();
- final String captionStr = caption.getText().toString();
-
- new AsyncTask<GpodnetService, Void, GpodnetDevice>() {
-
- private volatile Exception exception;
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- createNewDevice.setEnabled(false);
- chooseDevice.setEnabled(false);
- progBarCreateDevice.setVisibility(View.VISIBLE);
- txtvError.setVisibility(View.GONE);
- }
-
- @Override
- protected void onPostExecute(GpodnetDevice result) {
- super.onPostExecute(result);
- createNewDevice.setEnabled(true);
- chooseDevice.setEnabled(true);
- progBarCreateDevice.setVisibility(View.GONE);
- if (exception == null) {
- selectedDevice = result;
- advance();
- } else {
- txtvError.setText(exception.getMessage());
- txtvError.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- protected GpodnetDevice doInBackground(GpodnetService... params) {
- try {
- params[0].configureDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE);
- return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
- } catch (GpodnetServiceException e) {
- e.printStackTrace();
- exception = e;
- }
- return null;
- }
- }.execute(service);
- }
- }
- });
-
- chooseDevice.setOnClickListener(v -> {
- final int position = spinnerDevices.getSelectedItemPosition();
- if (position != AdapterView.INVALID_POSITION) {
- selectedDevice = devices.get().get(position);
- advance();
- }
- });
- }
-
-
- private String generateDeviceID(List<GpodnetDevice> gpodnetDevices) {
- // devices names must be of a certain form:
- // https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
- // This is more restrictive than needed, but I think it makes for more readable names.
- String baseId = Build.MODEL.replaceAll("\\W", "");
- String id = baseId;
- int num = 0;
-
- while (isDeviceWithIdInList(id, gpodnetDevices)) {
- id = baseId + "_" + num;
- num++;
- }
-
- return id;
- }
-
- private boolean isDeviceWithIdInList(String id, List<GpodnetDevice> gpodnetDevices) {
- if (gpodnetDevices == null) {
- return false;
- }
- for (GpodnetDevice device : gpodnetDevices) {
- if (device.getId().equals(id)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean checkDeviceIDText(EditText deviceID, EditText caption, TextView txtvError, List<GpodnetDevice> devices) {
- String text = deviceID.getText().toString();
- if (text.length() == 0) {
- txtvError.setText(R.string.gpodnetauth_device_errorEmpty);
- txtvError.setVisibility(View.VISIBLE);
- return false;
- } else if (caption.length() == 0) {
- txtvError.setText(R.string.gpodnetauth_device_caption_errorEmpty);
- txtvError.setVisibility(View.VISIBLE);
- return false;
- } else {
- if (devices != null) {
- if (isDeviceWithIdInList(text, devices)) {
- txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed);
- txtvError.setVisibility(View.VISIBLE);
- return false;
- }
- txtvError.setVisibility(View.GONE);
- return true;
- }
- return true;
- }
-
- }
-
- private void setupFinishView(View view) {
- final Button sync = view.findViewById(R.id.butSyncNow);
- final Button back = view.findViewById(R.id.butGoMainscreen);
-
- sync.setOnClickListener(v -> {
- finish();
- SyncService.sync(getApplicationContext());
- });
- back.setOnClickListener(v -> {
- Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- });
- }
-
- private void writeLoginCredentials() {
- if (BuildConfig.DEBUG) Log.d(TAG, "Writing login credentials");
- GpodnetPreferences.setUsername(username);
- GpodnetPreferences.setPassword(password);
- GpodnetPreferences.setDeviceID(selectedDevice.getId());
- }
-
- private void advance() {
- if (currentStep < STEP_FINISH) {
-
- View view = views[currentStep + 1];
- if (currentStep == STEP_DEFAULT) {
- setupLoginView(view);
- } else if (currentStep == STEP_LOGIN) {
- if (username == null || password == null) {
- throw new IllegalStateException("Username and password must not be null here");
- } else {
- setupDeviceView(view);
- }
- } else if (currentStep == STEP_DEVICE) {
- if (selectedDevice == null) {
- throw new IllegalStateException("Device must not be null here");
- } else {
- writeLoginCredentials();
- setupFinishView(view);
- }
- }
- if (currentStep != STEP_DEFAULT) {
- viewFlipper.showNext();
- }
- currentStep++;
- } else {
- finish();
- }
- }
-
- private boolean usernameHasUnwantedChars(String username) {
- Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
- Matcher containsUnwantedChars = special.matcher(username);
- return containsUnwantedChars.find();
- }
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java
index df81156b5..a267938d3 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java
@@ -20,14 +20,16 @@ import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.core.util.IntentUtils;
-import de.danoeh.antennapod.core.util.ThemeUtils;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
+import de.danoeh.antennapod.ui.common.CircularProgressBar;
public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapter.ChapterHolder> {
private Playable media;
private final Callback callback;
private final Context context;
private int currentChapterIndex = -1;
+ private long currentChapterPosition = -1;
private boolean hasImages = false;
public ChaptersListAdapter(Context context, Callback callback) {
@@ -48,7 +50,6 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
notifyDataSetChanged();
}
-
@Override
public void onBindViewHolder(@NonNull ChapterHolder holder, int position) {
Chapter sc = getItem(position);
@@ -83,8 +84,14 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
if (position == currentChapterIndex) {
int playingBackGroundColor = ThemeUtils.getColorFromAttr(context, R.attr.currently_playing_background);
holder.itemView.setBackgroundColor(playingBackGroundColor);
+ float progress = ((float) (currentChapterPosition - sc.getStart())) / duration;
+ progress = Math.max(progress, CircularProgressBar.MINIMUM_PERCENTAGE);
+ progress = Math.min(progress, CircularProgressBar.MAXIMUM_PERCENTAGE);
+ holder.progressBar.setPercentage(progress, position);
+ holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.av_replay));
} else {
holder.itemView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.transparent));
+ holder.progressBar.setPercentage(0, null);
}
if (hasImages) {
@@ -136,6 +143,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
final ImageView image;
final View secondaryActionButton;
final ImageView secondaryActionIcon;
+ final CircularProgressBar progressBar;
public ChapterHolder(@NonNull View itemView) {
super(itemView);
@@ -146,14 +154,23 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte
duration = itemView.findViewById(R.id.txtvDuration);
secondaryActionButton = itemView.findViewById(R.id.secondaryActionButton);
secondaryActionIcon = itemView.findViewById(R.id.secondaryActionIcon);
+ progressBar = itemView.findViewById(R.id.secondaryActionProgress);
}
}
public void notifyChapterChanged(int newChapterIndex) {
currentChapterIndex = newChapterIndex;
+ currentChapterPosition = getItem(newChapterIndex).getStart();
notifyDataSetChanged();
}
+ public void notifyTimeChanged(long timeMs) {
+ currentChapterPosition = timeMs;
+ // Passing an argument prevents flickering.
+ // See EpisodeItemListAdapter.notifyItemChangedCompat.
+ notifyItemChanged(currentChapterIndex, "foo");
+ }
+
private boolean ignoreChapter(Chapter c) {
return media.getDuration() > 0 && media.getDuration() < c.getStart();
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
index 7d195a9ad..d6801e1b5 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
@@ -1,23 +1,15 @@
package de.danoeh.antennapod.adapter;
import android.app.Activity;
-import android.content.Context;
-import android.os.Build;
-import android.text.Layout;
import android.text.format.DateUtils;
import android.util.Log;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
-import android.widget.TextView;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
-import com.joanzapata.iconify.widget.IconButton;
-import com.joanzapata.iconify.widget.IconTextView;
-
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
@@ -28,7 +20,7 @@ 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.DownloadRequester;
-import de.danoeh.antennapod.core.util.ThemeUtils;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.view.viewholder.DownloadItemViewHolder;
/**
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
index 540cd054c..9d63d8ad7 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
@@ -15,8 +15,8 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.download.Downloader;
-import de.danoeh.antennapod.core.util.ThemeUtils;
-import de.danoeh.antennapod.view.CircularProgressBar;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
+import de.danoeh.antennapod.ui.common.CircularProgressBar;
public class DownloadlistAdapter extends BaseAdapter {
@@ -65,6 +65,7 @@ public class DownloadlistAdapter extends BaseAdapter {
holder.title.setText(request.getTitle());
holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.navigation_cancel));
+ holder.secondaryActionButton.setContentDescription(context.getString(R.string.cancel_download_label));
holder.secondaryActionButton.setTag(downloader);
holder.secondaryActionButton.setOnClickListener(butSecondaryListener);
holder.secondaryActionProgress.setPercentage(0, request);
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 6bfd34d5c..8cb0fd30a 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedItemlistDescriptionAdapter.java
@@ -13,8 +13,6 @@ import androidx.annotation.Nullable;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.MediaType;
-import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.playback.RemoteMedia;
import de.danoeh.antennapod.core.feed.FeedItem;
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java
index 2e5ba31c9..dbb9ce0d0 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/FeedSearchResultAdapter.java
@@ -10,7 +10,7 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
-import de.danoeh.antennapod.view.SquareImageView;
+import de.danoeh.antennapod.ui.common.SquareImageView;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
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 92ed7b052..f8507ba74 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java
@@ -75,7 +75,7 @@ public class NavListAdapter extends BaseAdapter
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (key.equals(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)) {
+ if (UserPreferences.PREF_HIDDEN_DRAWER_ITEMS.equals(key)) {
loadItems();
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java
index 7ce086694..01712ea29 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java
@@ -6,7 +6,6 @@ import android.view.ContextMenu;
import android.view.MenuInflater;
import android.view.MotionEvent;
import android.view.View;
-import androidx.core.view.MotionEventCompat;
import androidx.recyclerview.widget.ItemTouchHelper;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java
index 8c294a9c9..919a4e217 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsAdapter.java
@@ -13,8 +13,6 @@ import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
-import com.bumptech.glide.Glide;
-
import java.lang.ref.WeakReference;
import java.text.NumberFormat;
import java.util.Locale;
@@ -23,7 +21,6 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
-import de.danoeh.antennapod.fragment.AddFeedFragment;
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
import jp.shts.android.library.TriangleLabelView;
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java
index 31dfe15da..78ea3b93f 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/PlayLocalActionButton.java
@@ -7,11 +7,8 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
-import de.danoeh.antennapod.core.preferences.UsageStatistics;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
-import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
-import de.danoeh.antennapod.dialog.StreamingConfirmationDialog;
public class PlayLocalActionButton extends ItemActionButton {
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java
index 7b8659968..e45280eed 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/VisitWebsiteActionButton.java
@@ -1,8 +1,6 @@
package de.danoeh.antennapod.adapter.actionbutton;
import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
import android.view.View;
import androidx.annotation.AttrRes;
import androidx.annotation.StringRes;
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java
index 3ac05e842..906d50c61 100644
--- a/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/DocumentFileExportWorker.java
@@ -9,10 +9,10 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
import de.danoeh.antennapod.core.export.ExportWriter;
import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.util.LangUtils;
import io.reactivex.Observable;
/**
@@ -44,7 +44,7 @@ public class DocumentFileExportWorker {
if (outputStream == null) {
throw new IOException();
}
- writer = new OutputStreamWriter(outputStream, LangUtils.UTF_8);
+ writer = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));
exportWriter.writeDocument(DBReader.getFeedList(), writer, context);
subscriber.onNext(output);
} catch (IOException e) {
diff --git a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java
index f81a52402..0930b59eb 100644
--- a/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java
+++ b/app/src/main/java/de/danoeh/antennapod/asynctask/ExportWorker.java
@@ -8,11 +8,11 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
import de.danoeh.antennapod.core.export.ExportWriter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
-import de.danoeh.antennapod.core.util.LangUtils;
import io.reactivex.Observable;
/**
@@ -47,7 +47,7 @@ public class ExportWorker {
return Observable.create(subscriber -> {
OutputStreamWriter writer = null;
try {
- writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8);
+ writer = new OutputStreamWriter(new FileOutputStream(output), Charset.forName("UTF-8"));
exportWriter.writeDocument(DBReader.getFeedList(), writer, context);
subscriber.onNext(output);
} catch (IOException e) {
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 6e584d34f..a45eb5199 100644
--- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java
+++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java
@@ -8,14 +8,13 @@ import de.danoeh.antennapod.core.ClientConfig;
*/
class ClientConfigurator {
- private ClientConfigurator(){}
+ private ClientConfigurator() {
+ }
static {
ClientConfig.USER_AGENT = "AntennaPod/" + BuildConfig.VERSION_NAME;
ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl();
ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl();
- ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl();
- ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl();
ClientConfig.castCallbacks = new CastCallbackImpl();
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java
deleted file mode 100644
index c3f7ae9c8..000000000
--- a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.danoeh.antennapod.config;
-
-import de.danoeh.antennapod.core.DBTasksCallbacks;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.storage.APDownloadAlgorithm;
-import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
-import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
-
-public class DBTasksCallbacksImpl implements DBTasksCallbacks {
-
- @Override
- public AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm() {
- return new APDownloadAlgorithm();
- }
-
- @Override
- public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() {
- return UserPreferences.getEpisodeCleanupAlgorithm();
- }
-}
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 71442f50b..f782308d1 100644
--- a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java
+++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java
@@ -30,8 +30,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks {
@Override
public PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request) {
final Intent activityIntent = new Intent(context.getApplicationContext(), DownloadAuthenticationActivity.class);
+ activityIntent.setAction("request" + request.getFeedfileId());
activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, request);
- activityIntent.putExtra(DownloadAuthenticationActivity.ARG_SEND_TO_DOWNLOAD_REQUESTER_BOOL, true);
return PendingIntent.getActivity(context.getApplicationContext(),
R.id.pending_intent_download_service_auth, activityIntent, PendingIntent.FLAG_ONE_SHOT);
}
@@ -54,9 +54,4 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks {
return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
-
- @Override
- public boolean shouldCreateReport() {
- return true;
- }
}
diff --git a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java
deleted file mode 100644
index 44f18e9da..000000000
--- a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package de.danoeh.antennapod.config;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.MainActivity;
-import de.danoeh.antennapod.activity.VideoplayerActivity;
-import de.danoeh.antennapod.core.PlaybackServiceCallbacks;
-import de.danoeh.antennapod.core.feed.MediaType;
-
-public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks {
- @Override
- public Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback) {
- if (mediaType == MediaType.AUDIO || remotePlayback) {
- return new Intent(context, MainActivity.class).putExtra(MainActivity.EXTRA_OPEN_PLAYER, true);
- } else {
- Intent i = new Intent(context, VideoplayerActivity.class);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- i.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
- }
- return i;
- }
- }
-
- @Override
- public boolean useQueue() {
- return true;
- }
-
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java
index 39d321f18..d7b2dc536 100644
--- a/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/AuthenticationDialog.java
@@ -1,37 +1,50 @@
package de.danoeh.antennapod.dialog;
import android.content.Context;
-import android.view.View;
-import android.widget.EditText;
+import android.text.method.HideReturnsTransformationMethod;
+import android.text.method.PasswordTransformationMethod;
+import android.view.LayoutInflater;
import androidx.appcompat.app.AlertDialog;
import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.databinding.AuthenticationDialogBinding;
/**
* Displays a dialog with a username and password text field and an optional checkbox to save username and preferences.
*/
public abstract class AuthenticationDialog extends AlertDialog.Builder {
+ boolean passwordHidden = true;
public AuthenticationDialog(Context context, int titleRes, boolean enableUsernameField,
String usernameInitialValue, String passwordInitialValue) {
super(context);
setTitle(titleRes);
- View rootView = View.inflate(context, R.layout.authentication_dialog, null);
- setView(rootView);
+ AuthenticationDialogBinding viewBinding = AuthenticationDialogBinding.inflate(LayoutInflater.from(context));
+ setView(viewBinding.getRoot());
- final EditText etxtUsername = rootView.findViewById(R.id.etxtUsername);
- final EditText etxtPassword = rootView.findViewById(R.id.etxtPassword);
-
- etxtUsername.setEnabled(enableUsernameField);
+ viewBinding.usernameEditText.setEnabled(enableUsernameField);
if (usernameInitialValue != null) {
- etxtUsername.setText(usernameInitialValue);
+ viewBinding.usernameEditText.setText(usernameInitialValue);
}
if (passwordInitialValue != null) {
- etxtPassword.setText(passwordInitialValue);
+ viewBinding.passwordEditText.setText(passwordInitialValue);
}
+ viewBinding.showPasswordButton.setOnClickListener(v -> {
+ if (passwordHidden) {
+ viewBinding.passwordEditText.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
+ viewBinding.showPasswordButton.setAlpha(1.0f);
+ } else {
+ viewBinding.passwordEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
+ viewBinding.showPasswordButton.setAlpha(0.6f);
+ }
+ passwordHidden = !passwordHidden;
+ });
+
setOnCancelListener(dialog -> onCancelled());
+ setOnDismissListener(dialog -> onCancelled());
setNegativeButton(R.string.cancel_label, null);
setPositiveButton(R.string.confirm_label, (dialog, which)
- -> onConfirmed(etxtUsername.getText().toString(), etxtPassword.getText().toString()));
+ -> onConfirmed(viewBinding.usernameEditText.getText().toString(),
+ viewBinding.passwordEditText.getText().toString()));
}
protected void onCancelled() {
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java
index 5aee3f2be..e1e8f1c2e 100644
--- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java
@@ -28,7 +28,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.SortOrder;
-import de.danoeh.antennapod.core.util.ThemeUtils;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -436,13 +436,18 @@ public class EpisodesApplyActionFragment extends Fragment implements Toolbar.OnM
}
private void deleteChecked() {
+ int countHasMedia = 0;
+ int countNoMedia = 0;
for (long id : checkedIds.toArray()) {
FeedItem episode = idMap.get(id);
- if (episode.hasMedia()) {
+ if (episode.hasMedia() && episode.getMedia().isDownloaded()) {
+ countHasMedia++;
DBWriter.deleteFeedMediaOfItem(getActivity(), episode.getMedia().getId());
+ } else {
+ countNoMedia++;
}
}
- close(R.plurals.deleted_episode_batch_label, checkedIds.size());
+ closeMore(R.plurals.deleted_multi_episode_batch_label, countNoMedia, countHasMedia);
}
private void close(@PluralsRes int msgId, int numItems) {
@@ -451,4 +456,12 @@ public class EpisodesApplyActionFragment extends Fragment implements Toolbar.OnM
getActivity().getSupportFragmentManager().popBackStack();
}
+ private void closeMore(@PluralsRes int msgId, int countNoMedia, int countHasMedia) {
+ ((MainActivity) getActivity()).showSnackbarAbovePlayer(
+ getResources().getQuantityString(msgId,
+ (countHasMedia + countNoMedia),
+ (countHasMedia + countNoMedia), countHasMedia),
+ Snackbar.LENGTH_LONG);
+ getActivity().getSupportFragmentManager().popBackStack();
+ }
}
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java
index 80df87891..779248e2f 100644
--- a/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java
@@ -16,7 +16,7 @@ import java.util.Set;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.FeedItemFilter;
import de.danoeh.antennapod.core.feed.FeedItemFilterGroup;
-import de.danoeh.antennapod.view.RecursiveRadioGroup;
+import de.danoeh.antennapod.ui.common.RecursiveRadioGroup;
public abstract class FilterDialog {
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java
deleted file mode 100644
index 8119dffcb..000000000
--- a/app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package de.danoeh.antennapod.dialog;
-
-import android.content.Context;
-import androidx.appcompat.app.AlertDialog;
-import android.text.Editable;
-import android.text.InputType;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
-import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
-
-/**
- * Creates a dialog that lets the user change the hostname for the gpodder.net service.
- */
-public class GpodnetSetHostnameDialog {
-
- private GpodnetSetHostnameDialog(){}
-
- private static final String TAG = "GpodnetSetHostnameDialog";
-
- public static AlertDialog createDialog(final Context context) {
- AlertDialog.Builder dialog = new AlertDialog.Builder(context);
- final EditText et = new EditText(context);
- et.setText(GpodnetPreferences.getHostname());
- et.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
- dialog.setTitle(R.string.pref_gpodnet_sethostname_title)
- .setView(setupContentView(context, et))
- .setPositiveButton(R.string.confirm_label, (dialog1, which) -> {
- final Editable e = et.getText();
- if (e != null) {
- GpodnetPreferences.setHostname(e.toString());
- }
- dialog1.dismiss();
- })
- .setNegativeButton(R.string.cancel_label, (dialog1, which) -> dialog1.cancel())
- .setNeutralButton(R.string.pref_gpodnet_sethostname_use_default_host, (dialog1, which) -> {
- GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST);
- dialog1.dismiss();
- })
- .setCancelable(true);
- return dialog.show();
- }
-
- private static View setupContentView(Context context, EditText et) {
- LinearLayout ll = new LinearLayout(context);
- ll.addView(et);
- LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) et.getLayoutParams();
- if (params != null) {
- params.setMargins(8, 8, 8, 8);
- params.width = ViewGroup.LayoutParams.MATCH_PARENT;
- params.height = ViewGroup.LayoutParams.MATCH_PARENT;
- }
- return ll;
- }
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java
index 699c6f492..a4e49ff96 100644
--- a/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/RenameFeedDialog.java
@@ -1,16 +1,15 @@
package de.danoeh.antennapod.dialog;
import android.app.Activity;
-import android.text.InputType;
import java.lang.ref.WeakReference;
import android.view.View;
-import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.databinding.EditTextDialogBinding;
public class RenameFeedDialog {
@@ -29,13 +28,14 @@ public class RenameFeedDialog {
}
View content = View.inflate(activity, R.layout.edit_text_dialog, null);
- EditText editText = content.findViewById(R.id.text);
- editText.setText(feed.getTitle());
+ EditTextDialogBinding alertViewBinding = EditTextDialogBinding.bind(content);
+
+ alertViewBinding.urlEditText.setText(feed.getTitle());
AlertDialog dialog = new AlertDialog.Builder(activity)
.setView(content)
.setTitle(de.danoeh.antennapod.core.R.string.rename_feed_label)
.setPositiveButton(android.R.string.ok, (d, input) -> {
- feed.setCustomTitle(editText.getText().toString());
+ feed.setCustomTitle(alertViewBinding.urlEditText.getText().toString());
DBWriter.setFeedCustomTitle(feed);
})
.setNeutralButton(de.danoeh.antennapod.core.R.string.reset, null)
@@ -44,7 +44,7 @@ public class RenameFeedDialog {
// To prevent cancelling the dialog on button click
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(
- (view) -> editText.setText(feed.getFeedTitle()));
+ (view) -> alertViewBinding.urlEditText.setText(feed.getFeedTitle()));
}
}
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 274c3b7bd..f1a41d753 100644
--- a/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/SleepTimerDialog.java
@@ -3,7 +3,6 @@ package de.danoeh.antennapod.dialog;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
@@ -88,6 +87,27 @@ public class SleepTimerDialog extends DialogFragment {
timeSetup = content.findViewById(R.id.timeSetup);
timeDisplay = content.findViewById(R.id.timeDisplay);
time = content.findViewById(R.id.time);
+ Button extendSleepFiveMinutesButton = content.findViewById(R.id.extendSleepFiveMinutesButton);
+ extendSleepFiveMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 5));
+ Button extendSleepTenMinutesButton = content.findViewById(R.id.extendSleepTenMinutesButton);
+ extendSleepTenMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 10));
+ Button extendSleepTwentyMinutesButton = content.findViewById(R.id.extendSleepTwentyMinutesButton);
+ extendSleepTwentyMinutesButton.setText(getString(R.string.extend_sleep_timer_label, 20));
+ extendSleepFiveMinutesButton.setOnClickListener(v -> {
+ if (controller != null) {
+ controller.extendSleepTimer(5 * 1000 * 60);
+ }
+ });
+ extendSleepTenMinutesButton.setOnClickListener(v -> {
+ if (controller != null) {
+ controller.extendSleepTimer(10 * 1000 * 60);
+ }
+ });
+ extendSleepTwentyMinutesButton.setOnClickListener(v -> {
+ if (controller != null) {
+ controller.extendSleepTimer(20 * 1000 * 60);
+ }
+ });
etxtTime.setText(SleepTimerPreferences.lastTimerValue());
etxtTime.postDelayed(() -> {
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 8a87fef25..29172bb5e 100644
--- a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java
+++ b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java
@@ -20,7 +20,7 @@ import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.feed.SubscriptionsFilter;
import de.danoeh.antennapod.core.feed.SubscriptionsFilterGroup;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.view.RecursiveRadioGroup;
+import de.danoeh.antennapod.ui.common.RecursiveRadioGroup;
public class SubscriptionsFilterDialog {
public static void showDialog(Context context) {
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 796ec556f..6e894176f 100644
--- a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java
+++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java
@@ -1,7 +1,5 @@
package de.danoeh.antennapod.discovery;
-import android.content.Context;
-import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java
index eeebd2ebf..8fbc8c76b 100644
--- a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java
+++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcher.java
@@ -1,8 +1,6 @@
package de.danoeh.antennapod.discovery;
import io.reactivex.Single;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.functions.Consumer;
import java.util.List;
public interface PodcastSearcher {
diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java
index ad574cab6..16c5548be 100644
--- a/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java
+++ b/app/src/main/java/de/danoeh/antennapod/discovery/PodcastSearcherRegistry.java
@@ -15,11 +15,11 @@ public class PodcastSearcherRegistry {
public static List<SearcherInfo> getSearchProviders() {
if (searchProviders == null) {
searchProviders = new ArrayList<>();
- searchProviders.add(new SearcherInfo(new CombinedSearcher(), 1.f));
- searchProviders.add(new SearcherInfo(new ItunesPodcastSearcher(), 1.f));
- searchProviders.add(new SearcherInfo(new FyydPodcastSearcher(), 1.f));
+ searchProviders.add(new SearcherInfo(new CombinedSearcher(), 1.0f));
searchProviders.add(new SearcherInfo(new GpodnetPodcastSearcher(), 0.0f));
- searchProviders.add(new SearcherInfo(new PodcastIndexPodcastSearcher(), 0.0f));
+ searchProviders.add(new SearcherInfo(new FyydPodcastSearcher(), 1.0f));
+ searchProviders.add(new SearcherInfo(new ItunesPodcastSearcher(), 1.0f));
+ searchProviders.add(new SearcherInfo(new PodcastIndexPodcastSearcher(), 1.0f));
}
return searchProviders;
}
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 09dfac37a..ed88214fe 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
@@ -12,7 +12,6 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -30,6 +29,8 @@ import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.util.SortOrder;
+import de.danoeh.antennapod.databinding.AddfeedBinding;
+import de.danoeh.antennapod.databinding.EditTextDialogBinding;
import de.danoeh.antennapod.discovery.CombinedSearcher;
import de.danoeh.antennapod.discovery.FyydPodcastSearcher;
import de.danoeh.antennapod.discovery.ItunesPodcastSearcher;
@@ -50,7 +51,7 @@ public class AddFeedFragment extends Fragment {
private static final int REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH = 1;
private static final int REQUEST_CODE_ADD_LOCAL_FOLDER = 2;
- private EditText combinedFeedSearchBox;
+ private AddfeedBinding viewBinding;
private MainActivity activity;
@Override
@@ -59,29 +60,30 @@ public class AddFeedFragment extends Fragment {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
- View root = inflater.inflate(R.layout.addfeed, container, false);
+ viewBinding = AddfeedBinding.inflate(getLayoutInflater());
activity = (MainActivity) getActivity();
- Toolbar toolbar = root.findViewById(R.id.toolbar);
+
+ Toolbar toolbar = viewBinding.toolbar;
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
- root.findViewById(R.id.btn_search_itunes).setOnClickListener(v
+ viewBinding.searchItunesButton.setOnClickListener(v
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(ItunesPodcastSearcher.class)));
- root.findViewById(R.id.btn_search_fyyd).setOnClickListener(v
+ viewBinding.searchFyydButton.setOnClickListener(v
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(FyydPodcastSearcher.class)));
- root.findViewById(R.id.btn_search_gpodder).setOnClickListener(v
+ viewBinding.searchGPodderButton.setOnClickListener(v
-> activity.loadChildFragment(new GpodnetMainFragment()));
- root.findViewById(R.id.btn_search_podcastindex).setOnClickListener(v
+ viewBinding.searchPodcastIndexButton.setOnClickListener(v
-> activity.loadChildFragment(OnlineSearchFragment.newInstance(PodcastIndexPodcastSearcher.class)));
- combinedFeedSearchBox = root.findViewById(R.id.combinedFeedSearchBox);
- combinedFeedSearchBox.setOnEditorActionListener((v, actionId, event) -> {
+ viewBinding.combinedFeedSearchEditText.setOnEditorActionListener((v, actionId, event) -> {
performSearch();
return true;
});
- root.findViewById(R.id.btn_add_via_url).setOnClickListener(v
+
+ viewBinding.addViaUrlButton.setOnClickListener(v
-> showAddViaUrlDialog());
- root.findViewById(R.id.btn_opml_import).setOnClickListener(v -> {
+ viewBinding.opmlImportButton.setOnClickListener(v -> {
try {
Intent intentGetContentAction = new Intent(Intent.ACTION_GET_CONTENT);
intentGetContentAction.addCategory(Intent.CATEGORY_OPENABLE);
@@ -93,7 +95,8 @@ public class AddFeedFragment extends Fragment {
.showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG);
}
});
- root.findViewById(R.id.btn_add_local_folder).setOnClickListener(v -> {
+
+ viewBinding.addLocalFolderButton.setOnClickListener(v -> {
if (Build.VERSION.SDK_INT < 21) {
return;
}
@@ -108,25 +111,29 @@ public class AddFeedFragment extends Fragment {
}
});
if (Build.VERSION.SDK_INT < 21) {
- root.findViewById(R.id.btn_add_local_folder).setVisibility(View.GONE);
+ viewBinding.addLocalFolderButton.setVisibility(View.GONE);
}
- root.findViewById(R.id.search_icon).setOnClickListener(view -> performSearch());
- return root;
+
+ viewBinding.searchButton.setOnClickListener(view -> performSearch());
+
+ return viewBinding.getRoot();
}
private void showAddViaUrlDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(R.string.add_podcast_by_url);
View content = View.inflate(getContext(), R.layout.edit_text_dialog, null);
- EditText editText = content.findViewById(R.id.text);
- editText.setHint(R.string.add_podcast_by_url_hint);
+ EditTextDialogBinding alertViewBinding = EditTextDialogBinding.bind(content);
+ alertViewBinding.urlEditText.setHint(R.string.add_podcast_by_url_hint);
+
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
String clipboardContent = clipboard.getText() != null ? clipboard.getText().toString() : "";
if (clipboardContent.trim().startsWith("http")) {
- editText.setText(clipboardContent.trim());
+ alertViewBinding.urlEditText.setText(clipboardContent.trim());
}
- builder.setView(content);
- builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> addUrl(editText.getText().toString()));
+ builder.setView(alertViewBinding.getRoot());
+ builder.setPositiveButton(R.string.confirm_label,
+ (dialog, which) -> addUrl(alertViewBinding.urlEditText.getText().toString()));
builder.setNegativeButton(R.string.cancel_label, null);
builder.show();
}
@@ -138,8 +145,7 @@ public class AddFeedFragment extends Fragment {
}
private void performSearch() {
- String query = combinedFeedSearchBox.getText().toString();
-
+ String query = viewBinding.combinedFeedSearchEditText.getText().toString();
if (query.matches("http[s]?://.*")) {
addUrl(query);
return;
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java
index ae3ba3a54..612959c04 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AllEpisodesFragment.java
@@ -4,7 +4,6 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.Menu;
-import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
@@ -105,13 +104,12 @@ public class AllEpisodesFragment extends EpisodesListFragment {
@NonNull
@Override
protected List<FeedItem> loadData() {
- return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE));
+ return DBReader.getRecentlyPublishedEpisodes(0, page * EPISODES_PER_PAGE, feedItemFilter);
}
@NonNull
@Override
protected List<FeedItem> loadMoreData() {
- return feedItemFilter.filter(DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE,
- EPISODES_PER_PAGE));
+ return DBReader.getRecentlyPublishedEpisodes((page - 1) * EPISODES_PER_PAGE, EPISODES_PER_PAGE, feedItemFilter);
}
}
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 5a65f956c..8ff8866e0 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java
@@ -1,8 +1,6 @@
package de.danoeh.antennapod.fragment;
-import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@@ -17,16 +15,21 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
+import androidx.cardview.widget.CardView;
import androidx.fragment.app.Fragment;
+import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
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.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils;
@@ -43,8 +46,7 @@ import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
import de.danoeh.antennapod.dialog.SleepTimerDialog;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
-import de.danoeh.antennapod.view.PagerIndicatorView;
-import de.danoeh.antennapod.view.PlaybackSpeedIndicatorView;
+import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView;
import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@@ -67,14 +69,12 @@ public class AudioPlayerFragment extends Fragment implements
private static final int POS_DESCR = 1;
private static final int POS_CHAPTERS = 2;
private static final int NUM_CONTENT_FRAGMENTS = 3;
- private static final String PREFS = "AudioPlayerFragmentPreferences";
- private static final String PREF_SHOW_TIME_LEFT = "showTimeLeft";
+ public static final String PREFS = "AudioPlayerFragmentPreferences";
private static final float EPSILON = 0.001f;
PlaybackSpeedIndicatorView butPlaybackSpeed;
TextView txtvPlaybackSpeed;
private ViewPager2 pager;
- private PagerIndicatorView pageIndicator;
private TextView txtvPosition;
private TextView txtvLength;
private SeekBar sbPosition;
@@ -86,10 +86,14 @@ public class AudioPlayerFragment extends Fragment implements
private ImageButton butSkip;
private Toolbar toolbar;
private ProgressBar progressIndicator;
+ private CardView cardViewSeek;
+ private TextView txtvSeek;
private PlaybackController controller;
private Disposable disposable;
private boolean showTimeLeft;
+ private boolean hasChapters = false;
+ private TabLayoutMediator tabLayoutMediator;
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@@ -120,6 +124,8 @@ public class AudioPlayerFragment extends Fragment implements
txtvFF = root.findViewById(R.id.txtvFF);
butSkip = root.findViewById(R.id.butSkip);
progressIndicator = root.findViewById(R.id.progLoading);
+ cardViewSeek = root.findViewById(R.id.cardViewSeek);
+ txtvSeek = root.findViewById(R.id.txtvSeek);
setupLengthTextView();
setupControlButtons();
@@ -141,13 +147,37 @@ public class AudioPlayerFragment extends Fragment implements
});
}
});
- pageIndicator = root.findViewById(R.id.page_indicator);
- pageIndicator.setViewPager(pager);
- pageIndicator.setOnClickListener(v ->
- pager.setCurrentItem((pager.getCurrentItem() + 1) % NUM_CONTENT_FRAGMENTS));
+
+ TabLayout tabLayout = root.findViewById(R.id.sliding_tabs);
+ tabLayoutMediator = new TabLayoutMediator(tabLayout, pager, (tab, position) -> {
+ tab.view.setAlpha(1.0f);
+ switch (position) {
+ case POS_COVER:
+ tab.setText(R.string.cover_label);
+ break;
+ case POS_DESCR:
+ tab.setText(R.string.description_label);
+ break;
+ case POS_CHAPTERS:
+ tab.setText(R.string.chapters_label);
+ if (!hasChapters) {
+ tab.view.setAlpha(0.5f);
+ }
+ break;
+ default:
+ break;
+ }
+ });
+ tabLayoutMediator.attach();
return root;
}
+ public void setHasChapters(boolean hasChapters) {
+ this.hasChapters = hasChapters;
+ tabLayoutMediator.detach();
+ tabLayoutMediator.attach();
+ }
+
public View getExternalPlayerHolder() {
return getView().findViewById(R.id.playerFragment);
}
@@ -185,16 +215,25 @@ public class AudioPlayerFragment extends Fragment implements
IntentUtils.sendLocalBroadcast(getActivity(), PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
}
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ public void onUnreadItemsUpdate(UnreadItemsUpdateEvent event) {
+ if (controller == null) {
+ return;
+ }
+ updatePosition(new PlaybackPositionEvent(controller.getPosition(),
+ controller.getDuration()));
+ }
+
private void setupLengthTextView() {
- SharedPreferences prefs = getContext().getSharedPreferences(PREFS, Context.MODE_PRIVATE);
- showTimeLeft = prefs.getBoolean(PREF_SHOW_TIME_LEFT, false);
+ showTimeLeft = UserPreferences.shouldShowRemainingTime();
txtvLength.setOnClickListener(v -> {
if (controller == null) {
return;
}
showTimeLeft = !showTimeLeft;
- prefs.edit().putBoolean(PREF_SHOW_TIME_LEFT, showTimeLeft).apply();
- updatePosition(new PlaybackPositionEvent(controller.getPosition(), controller.getDuration()));
+ UserPreferences.setShowRemainTimeSetting(showTimeLeft);
+ updatePosition(new PlaybackPositionEvent(controller.getPosition(),
+ controller.getDuration()));
});
}
@@ -362,10 +401,6 @@ public class AudioPlayerFragment extends Fragment implements
setupOptionsMenu(media);
}
- public void setHasChapters(boolean hasChapters) {
- pageIndicator.setDisabledPage(hasChapters ? -1 : 2);
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -411,6 +446,7 @@ public class AudioPlayerFragment extends Fragment implements
return;
}
txtvPosition.setText(Converter.getDurationStringLong(currentPosition));
+ showTimeLeft = UserPreferences.shouldShowRemainingTime();
if (showTimeLeft) {
txtvLength.setText("-" + Converter.getDurationStringLong(remainingTime));
} else {
@@ -432,22 +468,22 @@ public class AudioPlayerFragment extends Fragment implements
}
if (fromUser) {
float prog = progress / ((float) seekBar.getMax());
- int duration = controller.getDuration();
TimeSpeedConverter converter = new TimeSpeedConverter(controller.getCurrentPlaybackSpeedMultiplier());
- int position = converter.convert((int) (prog * duration));
- txtvPosition.setText(Converter.getDurationStringLong(position));
-
- if (showTimeLeft && prog != 0) {
- int timeLeft = converter.convert(duration - (int) (prog * duration));
- String length = "-" + Converter.getDurationStringLong(timeLeft);
- txtvLength.setText(length);
- }
+ int position = converter.convert((int) (prog * controller.getDuration()));
+ txtvSeek.setText(Converter.getDurationStringLong(position));
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
// interrupt position Observer, restart later
+ cardViewSeek.setScaleX(.8f);
+ cardViewSeek.setScaleY(.8f);
+ cardViewSeek.animate()
+ .setInterpolator(new FastOutSlowInInterpolator())
+ .alpha(1f).scaleX(1f).scaleY(1f)
+ .setDuration(200)
+ .start();
}
@Override
@@ -456,6 +492,13 @@ public class AudioPlayerFragment extends Fragment implements
float prog = seekBar.getProgress() / ((float) seekBar.getMax());
controller.seekTo((int) (prog * controller.getDuration()));
}
+ cardViewSeek.setScaleX(1f);
+ cardViewSeek.setScaleY(1f);
+ cardViewSeek.animate()
+ .setInterpolator(new FastOutSlowInInterpolator())
+ .alpha(0f).scaleX(.8f).scaleY(.8f)
+ .setDuration(200)
+ .start();
}
public void setupOptionsMenu(Playable media) {
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 6f95d71da..624345907 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java
@@ -8,9 +8,9 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChaptersListAdapter;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
@@ -45,7 +45,8 @@ public class ChaptersFragment extends Fragment {
RecyclerView recyclerView = root.findViewById(R.id.recyclerView);
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
- recyclerView.addItemDecoration(new HorizontalDividerItemDecoration.Builder(getActivity()).build());
+ recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(),
+ layoutManager.getOrientation()));
adapter = new ChaptersListAdapter(getActivity(), pos -> {
if (controller.getStatus() != PlayerStatus.PLAYING) {
@@ -106,6 +107,7 @@ public class ChaptersFragment extends Fragment {
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEventMainThread(PlaybackPositionEvent event) {
updateChapterSelection(getCurrentChapter(media));
+ adapter.notifyTimeChanged(event.getPosition());
}
private int getCurrentChapter(Playable media) {
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 59b2cd234..3519a34b4 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/CompletedDownloadsFragment.java
@@ -4,7 +4,6 @@ import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
-import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
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 648fc614a..60fcac03d 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java
@@ -13,11 +13,9 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
-
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
-
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.bitmap.FitCenter;
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
@@ -25,9 +23,11 @@ import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
+import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.util.ChapterUtils;
+import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
@@ -35,6 +35,7 @@ import io.reactivex.Maybe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
+import org.apache.commons.lang3.StringUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@@ -93,7 +94,12 @@ public class CoverFragment extends Fragment {
}
private void displayMediaInfo(@NonNull Playable media) {
- txtvPodcastTitle.setText(media.getFeedTitle());
+ String pubDateStr = DateUtils.formatAbbrev(getActivity(), ((FeedMedia) media).getPubDate());
+ txtvPodcastTitle.setText(StringUtils.stripToEmpty(media.getFeedTitle())
+ + "\u00A0"
+ + "・"
+ + "\u00A0"
+ + StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0"));
txtvEpisodeTitle.setText(media.getEpisodeTitle());
displayedChapterIndex = -2; // Force refresh
displayCoverImage(media.getPosition());
@@ -151,23 +157,25 @@ public class CoverFragment extends Fragment {
if (chapter != displayedChapterIndex) {
displayedChapterIndex = chapter;
+ RequestOptions options = new RequestOptions()
+ .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
+ .dontAnimate()
+ .transforms(new FitCenter(),
+ new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density)));
+
RequestBuilder<Drawable> cover = Glide.with(this)
.load(ImageResourceUtils.getImageLocation(media))
- .apply(new RequestOptions()
- .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
- .dontAnimate()
- .transforms(new FitCenter(),
- new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density))));
+ .error(Glide.with(this)
+ .load(ImageResourceUtils.getFallbackImageLocation(media))
+ .apply(options))
+ .apply(options);
+
if (chapter == -1 || TextUtils.isEmpty(media.getChapters().get(chapter).getImageUrl())) {
cover.into(imgvCover);
} else {
Glide.with(this)
.load(EmbeddedChapterImage.getModelFor(media, chapter))
- .apply(new RequestOptions()
- .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
- .dontAnimate()
- .transforms(new FitCenter(),
- new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density))))
+ .apply(options)
.thumbnail(cover)
.error(cover)
.into(imgvCover);
@@ -208,7 +216,7 @@ public class CoverFragment extends Fragment {
imgvCover.setLayoutParams(params);
}
} else {
- double percentageHeight = ratio * 0.8;
+ double percentageHeight = ratio * 0.6;
mainContainer.setOrientation(LinearLayout.HORIZONTAL);
if (newConfig.screenHeightDp > 0) {
params.height = (int) (convertDpToPixel(newConfig.screenHeightDp) * percentageHeight);
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 c2c45c581..2e11ea4ec 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java
@@ -4,7 +4,6 @@ import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
-import android.content.res.TypedArray;
import android.os.Bundle;
import androidx.annotation.NonNull;
@@ -13,7 +12,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.ListFragment;
import android.util.Log;
import android.view.Menu;
-import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
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 8dae310ba..39f935bbe 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/EpisodesListFragment.java
@@ -383,6 +383,14 @@ public abstract class EpisodesListFragment extends Fragment {
@NonNull
protected abstract List<FeedItem> loadData();
+ /**
+ * Load a new page of data as defined by {@link #page} and {@link #EPISODES_PER_PAGE}.
+ * If the number of items returned is less than {@link #EPISODES_PER_PAGE},
+ * it will be assumed that the underlying data is exhausted
+ * and this method will not be called again.
+ *
+ * @return The items from the next page of data
+ */
@NonNull
protected abstract List<FeedItem> loadMoreData();
}
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 5d701472f..f2fe23135 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
@@ -198,14 +198,19 @@ public class ExternalPlayerFragment extends Fragment {
feedName.setText(media.getFeedTitle());
onPositionObserverUpdate();
+ RequestOptions options = new RequestOptions()
+ .placeholder(R.color.light_gray)
+ .error(R.color.light_gray)
+ .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
+ .fitCenter()
+ .dontAnimate();
+
Glide.with(getActivity())
.load(ImageResourceUtils.getImageLocation(media))
- .apply(new RequestOptions()
- .placeholder(R.color.light_gray)
- .error(R.color.light_gray)
- .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
- .fitCenter()
- .dontAnimate())
+ .error(Glide.with(getActivity())
+ .load(ImageResourceUtils.getFallbackImageLocation(media))
+ .apply(options))
+ .apply(options)
.into(imgvCover);
if (controller != null && controller.isPlayingVideoLocally()) {
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 abb597e60..dd8a02893 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java
@@ -45,7 +45,7 @@ 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.ThemeUtils;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.core.util.syndication.HtmlToPlainText;
import de.danoeh.antennapod.fragment.preferences.StatisticsFragment;
import de.danoeh.antennapod.menuhandler.FeedMenuHandler;
@@ -130,6 +130,8 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
protected void doTint(Context themedContext) {
toolbar.getMenu().findItem(R.id.visit_website_item)
.setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.location_web_site));
+ toolbar.getMenu().findItem(R.id.share_parent)
+ .setIcon(ThemeUtils.getDrawableFromAttr(themedContext, R.attr.ic_share));
}
};
iconTintManager.updateTint();
@@ -284,9 +286,13 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
}
private void refreshToolbarState() {
+ boolean shareLinkVisible = feed != null && feed.getLink() != null;
+ boolean downloadUrlVisible = feed != null && !feed.isLocalFeed();
+
toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(feed != null && feed.isLocalFeed());
- toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(feed != null && !feed.isLocalFeed());
- toolbar.getMenu().findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null);
+ toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(downloadUrlVisible);
+ toolbar.getMenu().findItem(R.id.share_link_item).setVisible(shareLinkVisible);
+ toolbar.getMenu().findItem(R.id.share_parent).setVisible(downloadUrlVisible || shareLinkVisible);
toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null
&& IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink()))));
}
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 583d49afb..0bfb63718 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java
@@ -57,7 +57,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.FeedItemPermutors;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.Optional;
-import de.danoeh.antennapod.core.util.ThemeUtils;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil;
import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment;
import de.danoeh.antennapod.dialog.FilterDialog;
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 568b56304..fb9ac4a59 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedSettingsFragment.java
@@ -159,6 +159,7 @@ public class FeedSettingsFragment extends Fragment {
setupEpisodeFilterPreference();
setupPlaybackSpeedPreference();
setupFeedAutoSkipPreference();
+ setupEpisodeNotificationPreference();
updateAutoDeleteSummary();
updateVolumeReductionValue();
@@ -394,6 +395,19 @@ public class FeedSettingsFragment extends Fragment {
}
}
+ private void setupEpisodeNotificationPreference() {
+ SwitchPreferenceCompat pref = findPreference("episodeNotification");
+
+ pref.setChecked(feedPreferences.getShowEpisodeNotification());
+ pref.setOnPreferenceChangeListener((preference, newValue) -> {
+ boolean checked = newValue == Boolean.TRUE;
+ feedPreferences.setShowEpisodeNotification(checked);
+ feed.savePreferences();
+ pref.setChecked(checked);
+ return false;
+ });
+ }
+
private class ApplyToEpisodesDialog extends ConfirmationDialog {
private final boolean autoDownload;
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
index fc3052e20..18a61f1e6 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
@@ -8,10 +8,8 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.core.util.playback.Timeline;
import de.danoeh.antennapod.view.ShownotesWebView;
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 669dbdac2..2fc3d4f0e 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java
@@ -17,7 +17,6 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
import androidx.core.text.TextUtilsCompat;
import androidx.core.util.ObjectsCompat;
import androidx.core.view.ViewCompat;
@@ -58,7 +57,7 @@ import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DateUtils;
-import de.danoeh.antennapod.core.util.ThemeUtils;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.core.util.playback.Timeline;
import de.danoeh.antennapod.view.ShownotesWebView;
@@ -71,7 +70,6 @@ import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
-import java.util.Date;
import java.util.List;
import java.util.Locale;
@@ -120,6 +118,7 @@ public class ItemFragment extends Fragment {
private View butAction2;
private ItemActionButton actionButton1;
private ItemActionButton actionButton2;
+ private View noMediaLabel;
private Disposable disposable;
private PlaybackController controller;
@@ -169,6 +168,7 @@ public class ItemFragment extends Fragment {
butAction2Icon = layout.findViewById(R.id.butAction2Icon);
butAction1Text = layout.findViewById(R.id.butAction1Text);
butAction2Text = layout.findViewById(R.id.butAction2Text);
+ noMediaLabel = layout.findViewById(R.id.noMediaLabel);
butAction1.setOnClickListener(v -> {
if (actionButton1 instanceof StreamActionButton && !UserPreferences.isStreamOverDownload()
@@ -291,14 +291,19 @@ public class ItemFragment extends Fragment {
txtvPublished.setContentDescription(DateUtils.formatForAccessibility(getContext(), item.getPubDate()));
}
+ RequestOptions options = new RequestOptions()
+ .error(R.color.light_gray)
+ .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
+ .transforms(new FitCenter(),
+ new RoundedCorners((int) (4 * getResources().getDisplayMetrics().density)))
+ .dontAnimate();
+
Glide.with(getActivity())
.load(ImageResourceUtils.getImageLocation(item))
- .apply(new RequestOptions()
- .error(R.color.light_gray)
- .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
- .transforms(new FitCenter(),
- new RoundedCorners((int) (4 * getResources().getDisplayMetrics().density)))
- .dontAnimate())
+ .error(Glide.with(getActivity())
+ .load(ImageResourceUtils.getFallbackImageLocation(item))
+ .apply(options))
+ .apply(options)
.into(imgvCover);
updateButtons();
}
@@ -319,7 +324,9 @@ public class ItemFragment extends Fragment {
if (media == null) {
actionButton1 = new MarkAsPlayedActionButton(item);
actionButton2 = new VisitWebsiteActionButton(item);
+ noMediaLabel.setVisibility(View.VISIBLE);
} else {
+ noMediaLabel.setVisibility(View.GONE);
if (media.getDuration() > 0) {
txtvDuration.setText(Converter.getDurationStringLong(media.getDuration()));
txtvDuration.setContentDescription(
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java
index d748d14c9..1aa66dcbb 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/NewEpisodesFragment.java
@@ -1,7 +1,6 @@
package de.danoeh.antennapod.fragment;
import android.os.Bundle;
-import android.view.MenuInflater;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.ItemTouchHelper;
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 c994b4d8b..14f355b52 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/QuickFeedDiscoveryFragment.java
@@ -46,6 +46,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.
private FeedDiscoverAdapter adapter;
private GridView discoverGridLayout;
private TextView errorTextView;
+ private TextView poweredByTextView;
private LinearLayout errorView;
private Button errorRetry;
@@ -63,6 +64,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.
errorTextView = root.findViewById(R.id.discover_error_txtV);
errorRetry = root.findViewById(R.id.discover_error_retry_btn);
errorRetry.setOnClickListener((listener) -> loadToplist());
+ poweredByTextView = root.findViewById(R.id.discover_powered_by_itunes);
adapter = new FeedDiscoverAdapter((MainActivity) getActivity());
discoverGridLayout.setAdapter(adapter);
@@ -110,6 +112,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.
discoverGridLayout.setVisibility(View.INVISIBLE);
errorView.setVisibility(View.GONE);
errorRetry.setVisibility(View.INVISIBLE);
+ poweredByTextView.setVisibility(View.VISIBLE);
ItunesTopListLoader loader = new ItunesTopListLoader(getContext());
SharedPreferences prefs = getActivity().getSharedPreferences(ItunesTopListLoader.PREFS, MODE_PRIVATE);
@@ -122,6 +125,7 @@ public class QuickFeedDiscoveryFragment extends Fragment implements AdapterView.
progressBar.setVisibility(View.GONE);
discoverGridLayout.setVisibility(View.INVISIBLE);
errorRetry.setVisibility(View.INVISIBLE);
+ poweredByTextView.setVisibility(View.INVISIBLE);
return;
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
index 087abc327..fc500a223 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/RunningDownloadsFragment.java
@@ -6,7 +6,6 @@ import androidx.annotation.NonNull;
import androidx.fragment.app.ListFragment;
import android.util.Log;
import android.view.Menu;
-import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
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 0894c2df6..4735cab42 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java
@@ -27,6 +27,7 @@ import android.widget.TextView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.joanzapata.iconify.Iconify;
+import java.util.Locale;
import java.util.concurrent.Callable;
import de.danoeh.antennapod.R;
@@ -65,6 +66,12 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
public static final String TAG = "SubscriptionFragment";
private static final String PREFS = "SubscriptionFragment";
private static final String PREF_NUM_COLUMNS = "columns";
+ private static final int MIN_NUM_COLUMNS = 2;
+ private static final int[] COLUMN_CHECKBOX_IDS = {
+ R.id.subscription_num_columns_2,
+ R.id.subscription_num_columns_3,
+ R.id.subscription_num_columns_4,
+ R.id.subscription_num_columns_5};
private GridView subscriptionGridLayout;
private DBReader.NavDrawerData navDrawerData;
@@ -97,6 +104,11 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
toolbar.setOnMenuItemClickListener(this);
((MainActivity) getActivity()).setupToolbarToggle(toolbar);
toolbar.inflateMenu(R.menu.subscriptions);
+ for (int i = 0; i < COLUMN_CHECKBOX_IDS.length; i++) {
+ // Do this in Java to localize numbers
+ toolbar.getMenu().findItem(COLUMN_CHECKBOX_IDS[i])
+ .setTitle(String.format(Locale.getDefault(), "%d", i + MIN_NUM_COLUMNS));
+ }
refreshToolbarState();
subscriptionGridLayout = root.findViewById(R.id.subscriptions_grid);
@@ -119,10 +131,7 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem
private void refreshToolbarState() {
int columns = prefs.getInt(PREF_NUM_COLUMNS, getDefaultNumOfColumns());
- toolbar.getMenu().findItem(R.id.subscription_num_columns_2).setChecked(columns == 2);
- toolbar.getMenu().findItem(R.id.subscription_num_columns_3).setChecked(columns == 3);
- toolbar.getMenu().findItem(R.id.subscription_num_columns_4).setChecked(columns == 4);
- toolbar.getMenu().findItem(R.id.subscription_num_columns_5).setChecked(columns == 5);
+ toolbar.getMenu().findItem(COLUMN_CHECKBOX_IDS[columns - MIN_NUM_COLUMNS]).setChecked(true);
isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(),
R.id.refresh_item, updateRefreshMenuItemChecker);
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 e272b2869..1f5434688 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
@@ -4,12 +4,9 @@ import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
-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.widget.Button;
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java
index 0d6e79e84..ec61c82f2 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/AutoDownloadPreferencesFragment.java
@@ -174,7 +174,9 @@ public class AutoDownloadPreferencesFragment extends PreferenceFragmentCompat {
String[] entries = new String[values.length];
for (int x = 0; x < values.length; x++) {
int v = Integer.parseInt(values[x]);
- if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) {
+ if (v == UserPreferences.EPISODE_CLEANUP_EXCEPT_FAVORITE) {
+ entries[x] = res.getString(R.string.episode_cleanup_except_favorite_removal);
+ } else if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) {
entries[x] = res.getString(R.string.episode_cleanup_queue_removal);
} else if (v == UserPreferences.EPISODE_CLEANUP_NULL){
entries[x] = res.getString(R.string.episode_cleanup_never);
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java
new file mode 100644
index 000000000..187e8480b
--- /dev/null
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java
@@ -0,0 +1,302 @@
+package de.danoeh.antennapod.fragment.preferences;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Paint;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+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 de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
+import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
+import de.danoeh.antennapod.core.sync.SyncService;
+import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService;
+import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetDevice;
+import de.danoeh.antennapod.core.util.FileNameGenerator;
+import de.danoeh.antennapod.core.util.IntentUtils;
+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.
+ */
+public class GpodderAuthenticationFragment extends DialogFragment {
+ public static final String TAG = "GpodnetAuthActivity";
+
+ private ViewFlipper viewFlipper;
+
+ private static final int STEP_DEFAULT = -1;
+ private static final int STEP_HOSTNAME = 0;
+ private static final int STEP_LOGIN = 1;
+ private static final int STEP_DEVICE = 2;
+ private static final int STEP_FINISH = 3;
+
+ private int currentStep = -1;
+
+ private GpodnetService service;
+ private volatile String username;
+ private volatile String password;
+ private volatile GpodnetDevice selectedDevice;
+ private List<GpodnetDevice> devices;
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ AlertDialog.Builder dialog = new AlertDialog.Builder(getContext());
+ dialog.setTitle(GpodnetService.DEFAULT_BASE_HOST);
+ dialog.setNegativeButton(R.string.cancel_label, null);
+ dialog.setCancelable(false);
+ this.setCancelable(false);
+
+ View root = View.inflate(getContext(), R.layout.gpodnetauth_dialog, null);
+ viewFlipper = root.findViewById(R.id.viewflipper);
+ advance();
+ dialog.setView(root);
+
+ return dialog.create();
+ }
+
+ private void setupHostView(View view) {
+ final Button selectHost = view.findViewById(R.id.chooseHostButton);
+ final RadioGroup serverRadioGroup = view.findViewById(R.id.serverRadioGroup);
+ final EditText serverUrlText = view.findViewById(R.id.serverUrlText);
+ if (!GpodnetService.DEFAULT_BASE_HOST.equals(GpodnetPreferences.getHostname())) {
+ serverUrlText.setText(GpodnetPreferences.getHostname());
+ }
+ 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 -> {
+ if (serverRadioGroup.getCheckedRadioButtonId() == R.id.customServerRadio) {
+ GpodnetPreferences.setHostname(serverUrlText.getText().toString());
+ } else {
+ GpodnetPreferences.setHostname(GpodnetService.DEFAULT_BASE_HOST);
+ }
+ service = new GpodnetService(AntennapodHttpClient.getHttpClient(), GpodnetPreferences.getHostname());
+ getDialog().setTitle(GpodnetPreferences.getHostname());
+ advance();
+ });
+ }
+
+ private void setupLoginView(View view) {
+ final EditText username = view.findViewById(R.id.etxtUsername);
+ final EditText password = view.findViewById(R.id.etxtPassword);
+ final Button login = view.findViewById(R.id.butLogin);
+ final TextView txtvError = view.findViewById(R.id.credentialsError);
+ final ProgressBar progressBar = view.findViewById(R.id.progBarLogin);
+ final TextView createAccount = view.findViewById(R.id.createAccountButton);
+
+ createAccount.setPaintFlags(createAccount.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
+ createAccount.setOnClickListener(v -> IntentUtils.openInBrowser(getContext(), "https://gpodder.net/register/"));
+
+ password.setOnEditorActionListener((v, actionID, event) ->
+ actionID == EditorInfo.IME_ACTION_GO && login.performClick());
+
+ login.setOnClickListener(v -> {
+ final String usernameStr = username.getText().toString();
+ final String passwordStr = password.getText().toString();
+
+ if (usernameHasUnwantedChars(usernameStr)) {
+ txtvError.setText(R.string.gpodnetsync_username_characters_error);
+ txtvError.setVisibility(View.VISIBLE);
+ return;
+ }
+
+ login.setEnabled(false);
+ progressBar.setVisibility(View.VISIBLE);
+ txtvError.setVisibility(View.GONE);
+ InputMethodManager inputManager = (InputMethodManager) getContext()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputManager.hideSoftInputFromWindow(login.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+
+ Completable.fromAction(() -> {
+ service.authenticate(usernameStr, passwordStr);
+ devices = service.getDevices();
+ GpodderAuthenticationFragment.this.username = usernameStr;
+ GpodderAuthenticationFragment.this.password = passwordStr;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> {
+ login.setEnabled(true);
+ progressBar.setVisibility(View.GONE);
+ advance();
+ }, error -> {
+ login.setEnabled(true);
+ progressBar.setVisibility(View.GONE);
+ txtvError.setText(error.getCause().getMessage());
+ txtvError.setVisibility(View.VISIBLE);
+ });
+
+ });
+ }
+
+ private void setupDeviceView(View view) {
+ final EditText deviceName = view.findViewById(R.id.deviceName);
+ final LinearLayout devicesContainer = view.findViewById(R.id.devicesContainer);
+ deviceName.setText(generateDeviceName());
+
+ MaterialButton createDeviceButton = view.findViewById(R.id.createDeviceButton);
+ createDeviceButton.setOnClickListener(v -> createDevice(view));
+
+ for (GpodnetDevice device : devices) {
+ View row = View.inflate(getContext(), R.layout.gpodnetauth_device_row, null);
+ Button selectDeviceButton = row.findViewById(R.id.selectDeviceButton);
+ selectDeviceButton.setOnClickListener(v -> {
+ selectedDevice = device;
+ advance();
+ });
+ selectDeviceButton.setText(device.getCaption());
+ devicesContainer.addView(row);
+ }
+ }
+
+ private void createDevice(View view) {
+ final EditText deviceName = view.findViewById(R.id.deviceName);
+ final TextView txtvError = view.findViewById(R.id.deviceSelectError);
+ final ProgressBar progBarCreateDevice = view.findViewById(R.id.progbarCreateDevice);
+
+ String deviceNameStr = deviceName.getText().toString();
+ if (isDeviceInList(deviceNameStr)) {
+ return;
+ }
+ progBarCreateDevice.setVisibility(View.VISIBLE);
+ txtvError.setVisibility(View.GONE);
+ deviceName.setEnabled(false);
+
+ Observable.fromCallable(() -> {
+ String deviceId = generateDeviceId(deviceNameStr);
+ service.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE);
+ return new GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(device -> {
+ progBarCreateDevice.setVisibility(View.GONE);
+ selectedDevice = device;
+ advance();
+ }, error -> {
+ deviceName.setEnabled(true);
+ progBarCreateDevice.setVisibility(View.GONE);
+ txtvError.setText(error.getMessage());
+ txtvError.setVisibility(View.VISIBLE);
+ });
+ }
+
+ private String generateDeviceName() {
+ String baseName = getString(R.string.gpodnetauth_device_name_default, Build.MODEL);
+ String name = baseName;
+ int num = 1;
+ while (isDeviceInList(name)) {
+ name = baseName + " (" + num + ")";
+ num++;
+ }
+ return name;
+ }
+
+ private String generateDeviceId(String name) {
+ // devices names must be of a certain form:
+ // https://gpoddernet.readthedocs.org/en/latest/api/reference/general.html#devices
+ return FileNameGenerator.generateFileName(name).replaceAll("\\W", "_").toLowerCase(Locale.US);
+ }
+
+ private boolean isDeviceInList(String name) {
+ if (devices == null) {
+ return false;
+ }
+ String id = generateDeviceId(name);
+ for (GpodnetDevice device : devices) {
+ if (device.getId().equals(id) || device.getCaption().equals(name)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private GpodnetDevice findDevice(String id) {
+ if (devices == null) {
+ return null;
+ }
+ for (GpodnetDevice device : devices) {
+ if (device.getId().equals(id)) {
+ return device;
+ }
+ }
+ return null;
+ }
+
+ private void setupFinishView(View view) {
+ final Button sync = view.findViewById(R.id.butSyncNow);
+
+ sync.setOnClickListener(v -> {
+ dismiss();
+ SyncService.sync(getContext());
+ });
+ }
+
+ 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);
+ } else if (currentStep == STEP_HOSTNAME) {
+ setupLoginView(view);
+ } else if (currentStep == STEP_LOGIN) {
+ if (username == null || password == null) {
+ throw new IllegalStateException("Username and password must not be null here");
+ } else {
+ setupDeviceView(view);
+ }
+ } else if (currentStep == STEP_DEVICE) {
+ if (selectedDevice == null) {
+ throw new IllegalStateException("Device must not be null here");
+ } else {
+ writeLoginCredentials();
+ setupFinishView(view);
+ }
+ }
+ if (currentStep != STEP_DEFAULT) {
+ viewFlipper.showNext();
+ }
+ currentStep++;
+ } else {
+ dismiss();
+ }
+ }
+
+ private boolean usernameHasUnwantedChars(String username) {
+ Pattern special = Pattern.compile("[!@#$%&*()+=|<>?{}\\[\\]~]");
+ Matcher containsUnwantedChars = special.matcher(username);
+ return containsUnwantedChars.find();
+ }
+}
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
index bec73894c..4fb734e17 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java
@@ -1,15 +1,12 @@
package de.danoeh.antennapod.fragment.preferences;
import android.app.Activity;
-import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.core.text.HtmlCompat;
-import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import android.text.Spanned;
import android.text.format.DateUtils;
-import android.widget.Toast;
import com.google.android.material.snackbar.Snackbar;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.PreferenceActivity;
@@ -17,19 +14,16 @@ 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 de.danoeh.antennapod.dialog.GpodnetSetHostnameDialog;
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";
- private static final String PREF_GPODNET_HOSTNAME = "pref_gpodnet_hostname";
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -54,6 +48,7 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void syncStatusChanged(SyncServiceEvent event) {
+ updateGpodnetPreferenceScreen();
if (!GpodnetPreferences.loggedIn()) {
return;
}
@@ -69,6 +64,10 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
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,
@@ -97,11 +96,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
updateGpodnetPreferenceScreen();
return true;
});
- findPreference(PREF_GPODNET_HOSTNAME).setOnPreferenceClickListener(preference -> {
- GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(
- dialog -> updateGpodnetPreferenceScreen());
- return true;
- });
}
private void updateGpodnetPreferenceScreen() {
@@ -122,7 +116,6 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
} else {
findPreference(PREF_GPODNET_LOGOUT).setSummary(null);
}
- findPreference(PREF_GPODNET_HOSTNAME).setSummary(GpodnetPreferences.getHostname());
}
private void updateLastGpodnetSyncReport(boolean successful, long lastTime) {
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 99fd12021..7bf602e35 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,8 +1,11 @@
package de.danoeh.antennapod.fragment.preferences;
import android.content.Intent;
+import android.os.Build;
import android.os.Bundle;
+import android.provider.Settings;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.bytehamster.lib.preferencesearch.SearchConfiguration;
import com.bytehamster.lib.preferencesearch.SearchPreference;
@@ -20,7 +23,7 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
private static final String PREF_SCREEN_NETWORK = "prefScreenNetwork";
private static final String PREF_SCREEN_GPODDER = "prefScreenGpodder";
private static final String PREF_SCREEN_STORAGE = "prefScreenStorage";
- private static final String PREF_FAQ = "prefFaq";
+ private static final String PREF_DOCUMENTATION = "prefDocumentation";
private static final String PREF_VIEW_FORUM = "prefViewForum";
private static final String PREF_SEND_BUG_REPORT = "prefSendBugReport";
private static final String PREF_CATEGORY_PROJECT = "project";
@@ -35,10 +38,18 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
setupSearch();
// If you are writing a spin-off, please update the details on screens like "About" and "Report bug"
- // and afterwards remove the following lines.
+ // 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)) {
findPreference(PREF_CATEGORY_PROJECT).setVisible(false);
+ Preference copyrightNotice = new Preference(getContext());
+ 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);
}
}
@@ -70,10 +81,16 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
return true;
});
findPreference(PREF_NOTIFICATION).setOnPreferenceClickListener(preference -> {
- ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_notifications);
+ if (Build.VERSION.SDK_INT >= 26) {
+ Intent intent = new Intent();
+ intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, getActivity().getPackageName());
+ startActivity(intent);
+ } else {
+ ((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_notifications);
+ }
return true;
});
-
findPreference(PREF_ABOUT).setOnPreferenceClickListener(
preference -> {
getParentFragmentManager().beginTransaction().replace(R.id.content, new AboutFragment())
@@ -88,8 +105,8 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat {
return true;
}
);
- findPreference(PREF_FAQ).setOnPreferenceClickListener(preference -> {
- IntentUtils.openInBrowser(getContext(), "https://antennapod.org/faq.html");
+ findPreference(PREF_DOCUMENTATION).setOnPreferenceClickListener(preference -> {
+ IntentUtils.openInBrowser(getContext(), "https://antennapod.org/documentation/");
return true;
});
findPreference(PREF_VIEW_FORUM).setOnPreferenceClickListener(preference -> {
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java
index fcc37f644..3889034fa 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java
@@ -15,7 +15,6 @@ import org.apache.commons.lang3.ArrayUtils;
import java.util.Calendar;
import java.util.GregorianCalendar;
-import java.util.Locale;
import java.util.concurrent.TimeUnit;
public class NetworkPreferencesFragment extends PreferenceFragmentCompat {
@@ -72,13 +71,13 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat {
Context context = getActivity().getApplicationContext();
String val;
long interval = UserPreferences.getUpdateInterval();
- if(interval > 0) {
+ if (interval > 0) {
int hours = (int) TimeUnit.MILLISECONDS.toHours(interval);
- String hoursStr = context.getResources().getQuantityString(R.plurals.time_hours_quantified, hours, hours);
- val = String.format(context.getString(R.string.pref_autoUpdateIntervallOrTime_every), hoursStr);
+ val = context.getResources().getQuantityString(
+ R.plurals.pref_autoUpdateIntervallOrTime_every_hours, hours, hours);
} else {
int[] timeOfDay = UserPreferences.getUpdateTimeOfDay();
- if(timeOfDay.length == 2) {
+ if (timeOfDay.length == 2) {
Calendar cal = new GregorianCalendar();
cal.set(Calendar.HOUR_OF_DAY, timeOfDay[0]);
cal.set(Calendar.MINUTE, timeOfDay[1]);
@@ -97,8 +96,7 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat {
private void setParallelDownloadsText(int downloads) {
final Resources res = getActivity().getResources();
- String s = String.format(Locale.getDefault(), "%d%s",
- downloads, res.getString(R.string.parallel_downloads_suffix));
+ String s = res.getString(R.string.parallel_downloads, downloads);
findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS).setSummary(s);
}
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 689a72ba7..4d1b79965 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
@@ -9,11 +9,14 @@ 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.core.preferences.UserPreferences;
import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog;
import de.danoeh.antennapod.dialog.FeedSortDialog;
import de.danoeh.antennapod.fragment.NavDrawerFragment;
import org.apache.commons.lang3.ArrayUtils;
+import org.greenrobot.eventbus.EventBus;
import java.util.List;
@@ -37,8 +40,17 @@ public class UserInterfacePreferencesFragment extends PreferenceFragmentCompat {
(preference, newValue) -> {
getActivity().recreate();
return true;
- }
- );
+ });
+
+ findPreference(UserPreferences.PREF_SHOW_TIME_LEFT)
+ .setOnPreferenceChangeListener(
+ (preference, newValue) -> {
+ UserPreferences.setShowRemainTimeSetting((Boolean) newValue);
+ EventBus.getDefault().post(new UnreadItemsUpdateEvent());
+ EventBus.getDefault().post(new PlayerStatusEvent());
+ return true;
+ });
+
findPreference(UserPreferences.PREF_HIDDEN_DRAWER_ITEMS)
.setOnPreferenceClickListener(preference -> {
showDrawerPreferencesDialog();
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java
index b440d053b..0a64bbe71 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/AboutFragment.java
@@ -33,7 +33,7 @@ public class AboutFragment extends PreferenceFragmentCompat {
return true;
});
findPreference("about_privacy_policy").setOnPreferenceClickListener((preference) -> {
- IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy.html");
+ IntentUtils.openInBrowser(getContext(), "https://antennapod.org/privacy/");
return true;
});
findPreference("about_licenses").setOnPreferenceClickListener((preference) -> {
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java
index 60d9f95dd..b844234b7 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/DevelopersFragment.java
@@ -6,8 +6,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.adapter.SimpleIconListAdapter;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java
index 6db1389ea..d759a5ff2 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/SpecialThanksFragment.java
@@ -6,8 +6,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.adapter.SimpleIconListAdapter;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java
index e8d8e113b..b77c74de6 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/about/TranslatorsFragment.java
@@ -6,8 +6,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.ListFragment;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.adapter.SimpleIconListAdapter;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
index 9ceed9369..0086a75ab 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java
@@ -4,7 +4,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.util.Log;
import android.view.Menu;
-import android.view.MenuInflater;
import android.view.MenuItem;
import androidx.annotation.NonNull;
diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
index 9c54a529b..fbfdf537f 100644
--- a/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
+++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/MenuItemUtils.java
@@ -9,7 +9,7 @@ import androidx.appcompat.widget.SearchView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.util.ThemeUtils;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.fragment.SearchFragment;
import java.util.HashMap;
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 311f44881..03a8edbf0 100644
--- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java
+++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.preferences;
import android.content.Context;
import android.content.SharedPreferences;
+import android.view.KeyEvent;
import androidx.preference.PreferenceManager;
import de.danoeh.antennapod.BuildConfig;
@@ -92,5 +93,16 @@ public class PreferenceUpgrader {
if (oldVersion < 1080100) {
prefs.edit().putString(UserPreferences.PREF_VIDEO_BEHAVIOR, "pip").apply();
}
+ if (oldVersion < 2010300) {
+ // Migrate hardware button preferences
+ if (prefs.getBoolean("prefHardwareForwardButtonSkips", false)) {
+ prefs.edit().putString(UserPreferences.PREF_HARDWARE_FORWARD_BUTTON,
+ String.valueOf(KeyEvent.KEYCODE_MEDIA_NEXT)).apply();
+ }
+ if (prefs.getBoolean("prefHardwarePreviousButtonRestarts", false)) {
+ prefs.edit().putString(UserPreferences.PREF_HARDWARE_PREVIOUS_BUTTON,
+ String.valueOf(KeyEvent.KEYCODE_MEDIA_PREVIOUS)).apply();
+ }
+ }
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java
deleted file mode 100644
index f755a4c84..000000000
--- a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package de.danoeh.antennapod.view;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.View;
-import androidx.annotation.Nullable;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.core.util.ThemeUtils;
-
-public class CircularProgressBar extends View {
- private static final float EPSILON = 0.005f;
-
- private final Paint paintBackground = new Paint();
- private final Paint paintProgress = new Paint();
- private float percentage = 0;
- private float targetPercentage = 0;
- private Object tag = null;
- private final RectF bounds = new RectF();
-
- public CircularProgressBar(Context context) {
- super(context);
- setup();
- }
-
- public CircularProgressBar(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- setup();
- }
-
- public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setup();
- }
-
- private void setup() {
- paintBackground.setAntiAlias(true);
- paintBackground.setStyle(Paint.Style.STROKE);
-
- paintProgress.setAntiAlias(true);
- paintProgress.setStyle(Paint.Style.STROKE);
- paintProgress.setStrokeCap(Paint.Cap.ROUND);
-
- int color = ThemeUtils.getColorFromAttr(getContext(), R.attr.action_icon_color);
- paintProgress.setColor(color);
- paintBackground.setColor(color);
- }
-
- /**
- * Sets the percentage to be displayed.
- * @param percentage Number from 0 to 1
- * @param tag When the tag is the same as last time calling setPercentage, the update is animated
- */
- public void setPercentage(float percentage, Object tag) {
- targetPercentage = percentage;
-
- if (tag == null || !tag.equals(this.tag)) {
- // Do not animate
- this.percentage = percentage;
- this.tag = tag;
- }
- invalidate();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- float padding = getHeight() * 0.07f;
- paintBackground.setStrokeWidth(getHeight() * 0.02f);
- paintProgress.setStrokeWidth(padding);
- bounds.set(padding, padding, getWidth() - padding, getHeight() - padding);
- canvas.drawArc(bounds, 0, 360, false, paintBackground);
-
- if (percentage > EPSILON && 1 - percentage > EPSILON) {
- canvas.drawArc(bounds, -90, percentage * 360, false, paintProgress);
- }
-
- if (Math.abs(percentage - targetPercentage) > EPSILON) {
- float speed = 0.02f;
- if (Math.abs(targetPercentage - percentage) < 0.1 && targetPercentage > percentage) {
- speed = 0.006f;
- }
- float delta = Math.min(speed, Math.abs(targetPercentage - percentage));
- float direction = ((targetPercentage - percentage) > 0 ? 1f : -1f);
- percentage += delta * direction;
- invalidate();
- }
- }
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java b/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java
index 83d90f98b..fb1c533c5 100644
--- a/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java
+++ b/app/src/main/java/de/danoeh/antennapod/view/EpisodeItemListRecyclerView.java
@@ -6,9 +6,9 @@ import android.content.res.Configuration;
import android.util.AttributeSet;
import android.view.View;
import androidx.appcompat.view.ContextThemeWrapper;
+import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import com.yqritc.recyclerviewflexibledivider.HorizontalDividerItemDecoration;
import de.danoeh.antennapod.R;
import io.reactivex.annotations.Nullable;
@@ -39,7 +39,7 @@ public class EpisodeItemListRecyclerView extends RecyclerView {
layoutManager.setRecycleChildrenOnDetach(true);
setLayoutManager(layoutManager);
setHasFixedSize(true);
- addItemDecoration(new HorizontalDividerItemDecoration.Builder(getContext()).build());
+ addItemDecoration(new DividerItemDecoration(getContext(), layoutManager.getOrientation()));
setClipToPadding(false);
}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java b/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java
deleted file mode 100644
index 10ed98769..000000000
--- a/app/src/main/java/de/danoeh/antennapod/view/PagerIndicatorView.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package de.danoeh.antennapod.view;
-
-import android.animation.ArgbEvaluator;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.util.AttributeSet;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-import androidx.core.text.TextUtilsCompat;
-import androidx.core.view.ViewCompat;
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.viewpager2.widget.ViewPager2;
-
-import java.util.Locale;
-
-public class PagerIndicatorView extends View {
- private final Paint paint = new Paint();
- private float position = 0;
- private int numPages = 0;
- private int disabledPage = -1;
- private int circleColor = 0;
- private int circleColorHighlight = -1;
- private boolean isLocaleRtl = false;
-
- public PagerIndicatorView(Context context) {
- super(context);
- setup();
- }
-
- public PagerIndicatorView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- setup();
- }
-
- public PagerIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setup();
- }
-
- private void setup() {
- isLocaleRtl = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
- == ViewCompat.LAYOUT_DIRECTION_RTL;
-
- paint.setAntiAlias(true);
- paint.setStyle(Paint.Style.FILL);
-
- int[] colorAttrs = new int[]{android.R.attr.textColorSecondary};
- TypedArray a = getContext().obtainStyledAttributes(colorAttrs);
- circleColorHighlight = a.getColor(0, 0xffffffff);
- circleColor = (Integer) new ArgbEvaluator().evaluate(0.8f, 0x00ffffff, circleColorHighlight);
- a.recycle();
- }
-
- /**
- * Visual and logical position distinction only happens in RTL locales (e.g. Persian)
- * where pages positions are flipped thus it does nothing in LTR locales (e.g. English)
- */
- private float logicalPositionToVisual(float position) {
- return isLocaleRtl ? numPages - 1 - position : position;
- }
-
- public void setViewPager(ViewPager2 pager) {
- numPages = pager.getAdapter().getItemCount();
- pager.getAdapter().registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
- @Override
- public void onChanged() {
- numPages = pager.getAdapter().getItemCount();
- invalidate();
- }
- });
- pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- PagerIndicatorView.this.position = logicalPositionToVisual(
- position + positionOffset);
- invalidate();
- }
- });
- }
-
- public void setDisabledPage(int disabledPage) {
- this.disabledPage = (int) logicalPositionToVisual(disabledPage);
- invalidate();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- for (int i = 0; i < numPages; i++) {
- if ((int) Math.floor(position) == i) {
- // This is the current dot
- drawCircle(canvas, i, (float) (1 - (position - Math.floor(position))));
- } else if ((int) Math.ceil(position) == i) {
- // This is the next dot
- drawCircle(canvas, i, (float) (position - Math.floor(position)));
- } else {
- drawCircle(canvas, i, 0);
- }
- }
- }
-
- private void drawCircle(Canvas canvas, int position, float frac) {
- float availableHeight = canvas.getHeight() - getPaddingTop() - getPaddingBottom();
- float circleRadiusSmall = availableHeight * 0.26f;
- float circleRadiusBig = availableHeight * 0.35f;
- float circleRadiusDelta = (circleRadiusBig - circleRadiusSmall);
- float start = 0.5f * (canvas.getWidth() - numPages * 1.5f * availableHeight);
- paint.setStrokeWidth(availableHeight * 0.3f);
-
- if (position == disabledPage) {
- paint.setStyle(Paint.Style.STROKE);
- } else {
- paint.setStyle(Paint.Style.FILL_AND_STROKE);
- }
-
- paint.setColor((Integer) new ArgbEvaluator().evaluate(frac, circleColor, circleColorHighlight));
- canvas.drawCircle(start + (position * 1.5f + 0.75f) * availableHeight, 0.5f * availableHeight + getPaddingTop(),
- circleRadiusSmall + frac * circleRadiusDelta, paint);
- }
-} \ No newline at end of file
diff --git a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedIndicatorView.java b/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedIndicatorView.java
deleted file mode 100644
index d7f1eac1d..000000000
--- a/app/src/main/java/de/danoeh/antennapod/view/PlaybackSpeedIndicatorView.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package de.danoeh.antennapod.view;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.View;
-import androidx.annotation.Nullable;
-import de.danoeh.antennapod.R;
-
-public class PlaybackSpeedIndicatorView extends View {
- private static final float DEG_2_RAD = (float) (Math.PI / 180);
- private static final float PADDING_ANGLE = 30;
- private static final float VALUE_UNSET = -4242;
-
- private final Paint arcPaint = new Paint();
- private final Paint indicatorPaint = new Paint();
- private final Path trianglePath = new Path();
- private float angle = VALUE_UNSET;
- private float targetAngle = VALUE_UNSET;
- private float degreePerFrame = 2;
- private float paddingArc = 20;
- private float paddingIndicator = 10;
- private RectF arcBounds = new RectF();
-
- public PlaybackSpeedIndicatorView(Context context) {
- super(context);
- setup();
- }
-
- public PlaybackSpeedIndicatorView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- setup();
- }
-
- public PlaybackSpeedIndicatorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setup();
- }
-
- private void setup() {
- setSpeed(1.0f); // Set default angle to 1.0
- targetAngle = VALUE_UNSET; // Do not move to first value that is set externally
-
- int[] colorAttrs = new int[] {R.attr.action_icon_color };
- TypedArray a = getContext().obtainStyledAttributes(colorAttrs);
- arcPaint.setColor(a.getColor(0, 0xffffffff));
- indicatorPaint.setColor(a.getColor(0, 0xffffffff));
- a.recycle();
-
- arcPaint.setAntiAlias(true);
- arcPaint.setStyle(Paint.Style.STROKE);
- arcPaint.setStrokeCap(Paint.Cap.ROUND);
-
- indicatorPaint.setAntiAlias(true);
- indicatorPaint.setStyle(Paint.Style.FILL);
-
- trianglePath.setFillType(Path.FillType.EVEN_ODD);
- }
-
- public void setSpeed(float value) {
- float maxAnglePerDirection = 90 + 45 - 2 * paddingArc;
- // Speed values above 3 are probably not too common. Cap at 3 for better differentiation
- float normalizedValue = Math.min(2.5f, value - 0.5f) / 2.5f; // Linear between 0 and 1
- float target = -maxAnglePerDirection + 2 * maxAnglePerDirection * normalizedValue;
- if (targetAngle == VALUE_UNSET) {
- angle = target;
- }
- targetAngle = target;
- degreePerFrame = Math.abs(targetAngle - angle) / 20;
- invalidate();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- paddingArc = getMeasuredHeight() / 4.5f;
- paddingIndicator = getMeasuredHeight() / 6f;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- float radiusInnerCircle = getWidth() / 10f;
- canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, radiusInnerCircle, indicatorPaint);
-
- trianglePath.rewind();
- float bigRadius = getHeight() / 2f - paddingIndicator;
- trianglePath.moveTo(getWidth() / 2f + (float) (bigRadius * Math.sin((-angle + 180) * DEG_2_RAD)),
- getHeight() / 2f + (float) (bigRadius * Math.cos((-angle + 180) * DEG_2_RAD)));
- trianglePath.lineTo(getWidth() / 2f + (float) (radiusInnerCircle * Math.sin((-angle + 180 - 90) * DEG_2_RAD)),
- getHeight() / 2f + (float) (radiusInnerCircle * Math.cos((-angle + 180 - 90) * DEG_2_RAD)));
- trianglePath.lineTo(getWidth() / 2f + (float) (radiusInnerCircle * Math.sin((-angle + 180 + 90) * DEG_2_RAD)),
- getHeight() / 2f + (float) (radiusInnerCircle * Math.cos((-angle + 180 + 90) * DEG_2_RAD)));
- trianglePath.close();
- canvas.drawPath(trianglePath, indicatorPaint);
-
- arcPaint.setStrokeWidth(getHeight() / 15f);
- arcBounds.set(paddingArc, paddingArc, getWidth() - paddingArc, getHeight() - paddingArc);
- canvas.drawArc(arcBounds, -180 - 45, 90 + 45 + angle - PADDING_ANGLE, false, arcPaint);
- canvas.drawArc(arcBounds, -90 + PADDING_ANGLE + angle, 90 + 45 - PADDING_ANGLE - angle, false, arcPaint);
-
- if (Math.abs(angle - targetAngle) > 0.5 && targetAngle != VALUE_UNSET) {
- angle += Math.signum(targetAngle - angle) * Math.min(degreePerFrame, Math.abs(targetAngle - angle));
- invalidate();
- }
- }
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/RecursiveRadioGroup.java b/app/src/main/java/de/danoeh/antennapod/view/RecursiveRadioGroup.java
deleted file mode 100644
index ee5e7c51d..000000000
--- a/app/src/main/java/de/danoeh/antennapod/view/RecursiveRadioGroup.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package de.danoeh.antennapod.view;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.RadioButton;
-import java.util.ArrayList;
-
-/**
- * An alternative to {@link android.widget.RadioGroup} that allows to nest children.
- * Basend on https://stackoverflow.com/a/14309274.
- */
-public class RecursiveRadioGroup extends LinearLayout {
- private final ArrayList<RadioButton> radioButtons = new ArrayList<>();
- private RadioButton checkedButton = null;
-
- public RecursiveRadioGroup(Context context) {
- super(context);
- }
-
- public RecursiveRadioGroup(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public RecursiveRadioGroup(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- super.addView(child, index, params);
- parseChild(child);
- }
-
- public void parseChild(final View child) {
- if (child instanceof RadioButton) {
- RadioButton button = (RadioButton) child;
- radioButtons.add(button);
- button.setOnCheckedChangeListener((buttonView, isChecked) -> {
- if (!isChecked) {
- return;
- }
- checkedButton = (RadioButton) buttonView;
-
- for (RadioButton view : radioButtons) {
- if (view != buttonView) {
- view.setChecked(false);
- }
- }
- });
- } else if (child instanceof ViewGroup) {
- parseChildren((ViewGroup) child);
- }
- }
-
- public void parseChildren(final ViewGroup child) {
- for (int i = 0; i < child.getChildCount(); i++) {
- parseChild(child.getChildAt(i));
- }
- }
-
- public RadioButton getCheckedButton() {
- return checkedButton;
- }
-}
diff --git a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java b/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java
deleted file mode 100644
index c256ede9e..000000000
--- a/app/src/main/java/de/danoeh/antennapod/view/SquareImageView.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package de.danoeh.antennapod.view;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import androidx.appcompat.widget.AppCompatImageView;
-import android.util.AttributeSet;
-import de.danoeh.antennapod.core.R;
-
-/**
- * From http://stackoverflow.com/a/19449488/6839
- */
-public class SquareImageView extends AppCompatImageView {
- public static final int DIRECTION_WIDTH = 0;
- public static final int DIRECTION_HEIGHT = 1;
- public static final int DIRECTION_MINIMUM = 2;
-
- private int direction = DIRECTION_WIDTH;
-
- public SquareImageView(Context context) {
- super(context);
- }
-
- public SquareImageView(Context context, AttributeSet attrs) {
- super(context, attrs);
- loadAttrs(context, attrs);
- }
-
- public SquareImageView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- loadAttrs(context, attrs);
- }
-
- private void loadAttrs(Context context, AttributeSet attrs) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SquareImageView);
- direction = a.getInt(R.styleable.SquareImageView_direction, DIRECTION_WIDTH);
- a.recycle();
- }
-
- public void setDirection(int direction) {
- this.direction = direction;
- requestLayout();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- switch (direction) {
- case DIRECTION_MINIMUM:
- int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
- setMeasuredDimension(size, size);
- break;
- case DIRECTION_HEIGHT:
- setMeasuredDimension(getMeasuredHeight(), getMeasuredHeight());
- break;
- default:
- setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth());
- break;
- }
- }
-
-} \ No newline at end of file
diff --git a/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java b/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java
deleted file mode 100644
index 37792b4d1..000000000
--- a/app/src/main/java/de/danoeh/antennapod/view/WrappingGridView.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package de.danoeh.antennapod.view;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.GridView;
-
-/**
- * Source: https://stackoverflow.com/a/46350213/
- */
-public class WrappingGridView extends GridView {
-
- public WrappingGridView(Context context) {
- super(context);
- }
-
- public WrappingGridView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public WrappingGridView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int heightSpec = heightMeasureSpec;
- if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
- // The great Android "hackatlon", the love, the magic.
- // The two leftmost bits in the height measure spec have
- // a special meaning, hence we can't use them to describe height.
- heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
- }
- super.onMeasure(widthMeasureSpec, heightSpec);
- }
-}
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 35744227f..0e84afe04 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
@@ -13,9 +13,7 @@ import android.widget.TextView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
-
import com.joanzapata.iconify.Iconify;
-
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.adapter.CoverLoader;
@@ -25,13 +23,15 @@ import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.NetworkUtils;
-import de.danoeh.antennapod.core.util.ThemeUtils;
-import de.danoeh.antennapod.view.CircularProgressBar;
+import de.danoeh.antennapod.ui.common.ThemeUtils;
+import de.danoeh.antennapod.ui.common.CircularProgressBar;
/**
* Holds the view which shows FeedItems.
@@ -132,9 +132,6 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
private void bind(FeedMedia media) {
isVideo.setVisibility(media.getMediaType() == MediaType.VIDEO ? View.VISIBLE : View.GONE);
duration.setVisibility(media.getDuration() > 0 ? View.VISIBLE : View.GONE);
- duration.setText(Converter.getDurationStringLong(media.getDuration()));
- duration.setContentDescription(activity.getString(R.string.chapter_duration,
- Converter.getDurationStringLocalized(activity, media.getDuration())));
if (media.isCurrentlyPlaying()) {
itemView.setBackgroundColor(ThemeUtils.getColorFromAttr(activity, R.attr.currently_playing_background));
@@ -152,6 +149,9 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
secondaryActionProgress.setPercentage(0, item); // Animate X% -> 0%
}
+ duration.setText(Converter.getDurationStringLong(media.getDuration()));
+ duration.setContentDescription(activity.getString(R.string.chapter_duration,
+ Converter.getDurationStringLocalized(activity, media.getDuration())));
if (item.getState() == FeedItem.State.PLAYING || item.getState() == FeedItem.State.IN_PROGRESS) {
int progress = (int) (100.0 * media.getPosition() / media.getDuration());
progressBar.setProgress(progress);
@@ -160,6 +160,11 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
Converter.getDurationStringLocalized(activity, media.getPosition())));
progressBar.setVisibility(View.VISIBLE);
position.setVisibility(View.VISIBLE);
+ if (UserPreferences.shouldShowRemainingTime()) {
+ duration.setText("-" + Converter.getDurationStringLong(media.getDuration() - media.getPosition()));
+ duration.setContentDescription(activity.getString(R.string.chapter_duration,
+ Converter.getDurationStringLocalized(activity, (media.getDuration() - media.getPosition()))));
+ }
} else {
progressBar.setVisibility(View.GONE);
position.setVisibility(View.GONE);
@@ -186,6 +191,22 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
}
}
+ private void updateDuration(PlaybackPositionEvent event) {
+ int currentPosition = event.getPosition();
+ int timeDuration = event.getDuration();
+ int remainingTime = event.getDuration() - event.getPosition();
+ Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
+ if (currentPosition == PlaybackService.INVALID_TIME || timeDuration == PlaybackService.INVALID_TIME) {
+ Log.w(TAG, "Could not react to position observer update because of invalid time");
+ return;
+ }
+ if (UserPreferences.shouldShowRemainingTime()) {
+ duration.setText("-" + Converter.getDurationStringLong(remainingTime));
+ } else {
+ duration.setText(Converter.getDurationStringLong(timeDuration));
+ }
+ }
+
public FeedItem getFeedItem() {
return item;
}
@@ -197,7 +218,7 @@ public class EpisodeItemViewHolder extends RecyclerView.ViewHolder {
public void notifyPlaybackPositionUpdated(PlaybackPositionEvent event) {
progressBar.setProgress((int) (100.0 * event.getPosition() / event.getDuration()));
position.setText(Converter.getDurationStringLong(event.getPosition()));
- duration.setText(Converter.getDurationStringLong(event.getDuration()));
+ updateDuration(event);
duration.setVisibility(View.VISIBLE); // Even if the duration was previously unknown, it is now known
}
diff --git a/app/src/main/res/layout/activity_widget_config.xml b/app/src/main/res/layout/activity_widget_config.xml
index ca8aba52d..6e31aec0d 100644
--- a/app/src/main/res/layout/activity_widget_config.xml
+++ b/app/src/main/res/layout/activity_widget_config.xml
@@ -22,7 +22,7 @@
android:id="@+id/widget_config_preview"
layout="@layout/player_widget"
android:layout_width="match_parent"
- android:layout_height="80dp"
+ android:layout_height="96dp"
android:layout_gravity="center"
android:layout_margin="16dp" />
</FrameLayout>
@@ -68,13 +68,38 @@
android:max="100"
android:progress="100" />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <CheckBox
+ android:id="@+id/ckRewind"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Rewind" />
+
+ <CheckBox
+ android:id="@+id/ckFastForward"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Forward" />
+
+ <CheckBox
+ android:id="@+id/ckSkip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Skip" />
+ </LinearLayout>
<Button
android:id="@+id/butConfirm"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/widget_create_button" />
-
</LinearLayout>
</LinearLayout>
diff --git a/app/src/main/res/layout/addfeed.xml b/app/src/main/res/layout/addfeed.xml
index ee57fdbed..d6ebd58d7 100644
--- a/app/src/main/res/layout/addfeed.xml
+++ b/app/src/main/res/layout/addfeed.xml
@@ -35,11 +35,11 @@
android:layout_marginRight="8dp"
android:contentDescription="@string/search_podcast_hint"
app:srcCompat="?attr/action_search"
- android:id="@+id/search_icon"
+ android:id="@+id/searchButton"
android:scaleType="center"/>
<EditText
- android:id="@+id/combinedFeedSearchBox"
+ android:id="@+id/combinedFeedSearchEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
@@ -87,7 +87,7 @@
android:textColor="?android:attr/textColorPrimary"/>
<TextView
- android:id="@+id/btn_add_via_url"
+ android:id="@+id/addViaUrlButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="?attr/feed"
@@ -96,7 +96,7 @@
android:text="@string/add_podcast_by_url"/>
<TextView
- android:id="@+id/btn_add_local_folder"
+ android:id="@+id/addLocalFolderButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="?attr/ic_folder"
@@ -105,7 +105,7 @@
android:text="@string/add_local_folder"/>
<TextView
- android:id="@+id/btn_search_itunes"
+ android:id="@+id/searchItunesButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="?attr/action_search"
@@ -114,7 +114,7 @@
android:text="@string/search_itunes_label"/>
<TextView
- android:id="@+id/btn_search_fyyd"
+ android:id="@+id/searchFyydButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="?attr/action_search"
@@ -123,7 +123,7 @@
android:text="@string/search_fyyd_label"/>
<TextView
- android:id="@+id/btn_search_gpodder"
+ android:id="@+id/searchGPodderButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="?attr/action_search"
@@ -132,7 +132,7 @@
android:text="@string/browse_gpoddernet_label"/>
<TextView
- android:id="@+id/btn_search_podcastindex"
+ android:id="@+id/searchPodcastIndexButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="?attr/action_search"
@@ -141,7 +141,7 @@
android:text="@string/search_podcastindex_label"/>
<TextView
- android:id="@+id/btn_opml_import"
+ android:id="@+id/opmlImportButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:drawableStartCompat="?attr/av_download"
diff --git a/app/src/main/res/layout/audioplayer_fragment.xml b/app/src/main/res/layout/audioplayer_fragment.xml
index 225fdd98c..62e0a5e3a 100644
--- a/app/src/main/res/layout/audioplayer_fragment.xml
+++ b/app/src/main/res/layout/audioplayer_fragment.xml
@@ -15,15 +15,15 @@
app:navigationIcon="?homeAsUpIndicator"
android:id="@+id/toolbar"/>
- <de.danoeh.antennapod.view.PagerIndicatorView
- android:id="@+id/page_indicator"
- android:layout_height="16dp"
- android:layout_width="40dp"
- android:layout_marginTop="-12dp"
- android:padding="4dp"
+ <com.google.android.material.tabs.TabLayout
+ android:id="@+id/sliding_tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:layout_below="@id/toolbar"
- android:contentDescription="@string/switch_pages"
- android:layout_centerHorizontal="true"/>
+ android:background="?android:attr/windowBackground"
+ app:tabBackground="?attr/selectableItemBackground"
+ app:tabMode="fixed"
+ app:tabGravity="fill"/>
<FrameLayout
android:id="@+id/playerFragment"
@@ -39,7 +39,7 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_above="@id/playtime_layout"
- android:layout_below="@id/toolbar"
+ android:layout_below="@id/sliding_tabs"
android:foreground="?android:windowContentOverlay"
android:layout_marginBottom="12dp"/>
@@ -51,6 +51,34 @@
app:tint="?android:attr/windowBackground"
android:importantForAccessibility="no"/>
+ <androidx.cardview.widget.CardView
+ android:id="@+id/cardViewSeek"
+ android:alpha="0"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignBottom="@+id/pager"
+ android:layout_centerHorizontal="true"
+ android:layout_marginBottom="12dp"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="?attr/seek_background"
+ app:cardElevation="0dp"
+ tools:alpha="1">
+
+ <TextView
+ android:id="@+id/txtvSeek"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="24dp"
+ android:paddingTop="4dp"
+ android:paddingRight="24dp"
+ android:paddingBottom="4dp"
+ android:textColor="@color/white"
+ android:textSize="24sp"
+ tools:text="1:06:29" />
+
+ </androidx.cardview.widget.CardView>
+
<LinearLayout
android:id="@+id/playtime_layout"
android:layout_width="match_parent"
@@ -128,7 +156,7 @@
android:scaleType="fitCenter"
tools:srcCompat="@drawable/ic_av_play_white_24dp"/>
- <de.danoeh.antennapod.view.CircularProgressBar
+ <de.danoeh.antennapod.ui.common.CircularProgressBar
android:layout_width="@dimen/audioplayer_playercontrols_length_big"
android:layout_height="@dimen/audioplayer_playercontrols_length_big"
android:layout_marginLeft="16dp"
@@ -136,7 +164,8 @@
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp"
android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"/>
+ android:layout_centerVertical="true"
+ app:foregroundColor="?attr/action_icon_color"/>
<ProgressBar
style="?android:attr/progressBarStyle"
@@ -177,7 +206,7 @@
android:textColor="?android:attr/textColorSecondary"
android:clickable="false"/>
- <de.danoeh.antennapod.view.PlaybackSpeedIndicatorView
+ <de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView
android:id="@+id/butPlaybackSpeed"
android:layout_width="@dimen/audioplayer_playercontrols_length"
android:layout_height="@dimen/audioplayer_playercontrols_length"
@@ -186,7 +215,8 @@
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/playback_speed"
- tools:srcCompat="@drawable/ic_playback_speed_white"/>
+ tools:srcCompat="@drawable/ic_playback_speed_white"
+ app:foregroundColor="?attr/action_icon_color"/>
<TextView
android:id="@+id/txtvPlaybackSpeed"
diff --git a/app/src/main/res/layout/authentication_dialog.xml b/app/src/main/res/layout/authentication_dialog.xml
index 9c6f3e2bb..f311fc1dd 100644
--- a/app/src/main/res/layout/authentication_dialog.xml
+++ b/app/src/main/res/layout/authentication_dialog.xml
@@ -1,30 +1,59 @@
<?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="match_parent"
- android:orientation="vertical" >
-
- <EditText
- android:id="@+id/etxtUsername"
- android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:layout_margin="16dp"
- android:hint="@string/username_label"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:cursorVisible="true"/>
-
- <EditText
- android:id="@+id/etxtPassword"
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:layout_margin="16dp"
- android:inputType="textPassword"
- android:hint="@string/password_label"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:cursorVisible="true"/>
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="16dp">
+
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="8dp">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/usernameEditText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/username_label"
+ android:lines="1"/>
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/passwordEditText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/password_label"
+ android:inputType="textPassword"
+ android:lines="1"/>
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <com.joanzapata.iconify.widget.IconTextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/showPasswordButton"
+ android:text="{fa-eye}"
+ android:padding="8dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:alpha="0.6"
+ android:textSize="20sp"
+ android:layout_marginLeft="8dp"
+ android:layout_marginStart="8dp"/>
+ </LinearLayout>
</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/bug_report.xml b/app/src/main/res/layout/bug_report.xml
index 1cc9bc9d8..e97e85265 100644
--- a/app/src/main/res/layout/bug_report.xml
+++ b/app/src/main/res/layout/bug_report.xml
@@ -16,12 +16,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
- <Button
- android:id="@+id/btn_export_logcat"
- android:text="@string/export_logs"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
-
<TextView
android:layout_marginTop="8dp"
android:id="@+id/crash_report_logs"
diff --git a/app/src/main/res/layout/cover_fragment.xml b/app/src/main/res/layout/cover_fragment.xml
index 5460d0609..0ec46cbcd 100644
--- a/app/src/main/res/layout/cover_fragment.xml
+++ b/app/src/main/res/layout/cover_fragment.xml
@@ -10,7 +10,7 @@
android:padding="8dp"
android:gravity="center">
- <de.danoeh.antennapod.view.SquareImageView
+ <de.danoeh.antennapod.ui.common.SquareImageView
android:id="@+id/imgvCover"
android:layout_width="0dp"
android:layout_height="200dp"
diff --git a/app/src/main/res/layout/download_authentication_activity.xml b/app/src/main/res/layout/download_authentication_activity.xml
deleted file mode 100644
index e16a8b3a8..000000000
--- a/app/src/main/res/layout/download_authentication_activity.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?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="match_parent"
- android:orientation="vertical"
- android:padding="16dp">
-
- <TextView
- android:id="@+id/txtvTitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/authentication_notification_title"
- android:textSize="@dimen/text_size_large"
- android:textColor="?android:attr/textColorPrimary"/>
-
- <TextView
- android:id="@+id/txtvDescription"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/authentication_notification_msg"
- android:textColor="?android:attr/textColorSecondary"/>
-
- <EditText
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/etxtUsername"
- android:hint="@string/username_label"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:cursorVisible="true"/>
-
- <EditText
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/etxtPassword"
- android:hint="@string/password_label"
- android:inputType="textPassword"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:cursorVisible="true"/>
-
- <LinearLayout
- android:layout_width="fill_parent"
- android:layout_height="48dp"
- android:orientation="horizontal"
- android:gravity="end">
-
- <Button
- android:id="@+id/butCancel"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/cancel_label"
- style="@style/Widget.MaterialComponents.Button.TextButton"/>
-
- <Button
- android:id="@+id/butConfirm"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/confirm_label"
- style="@style/Widget.MaterialComponents.Button.TextButton"/>
- </LinearLayout>
-
-</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/edit_text_dialog.xml b/app/src/main/res/layout/edit_text_dialog.xml
index 6bf0bc6cb..b442b92ce 100644
--- a/app/src/main/res/layout/edit_text_dialog.xml
+++ b/app/src/main/res/layout/edit_text_dialog.xml
@@ -10,6 +10,6 @@
android:layout_height="wrap_content"
android:inputType="text"
android:ems="10"
- android:id="@+id/text" />
+ android:id="@+id/urlEditText" />
</LinearLayout>
diff --git a/app/src/main/res/layout/feeditem_fragment.xml b/app/src/main/res/layout/feeditem_fragment.xml
index 72effc585..049182803 100644
--- a/app/src/main/res/layout/feeditem_fragment.xml
+++ b/app/src/main/res/layout/feeditem_fragment.xml
@@ -166,6 +166,15 @@
</LinearLayout>
</LinearLayout>
+ <TextView
+ android:id="@+id/noMediaLabel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:textAlignment="center"
+ android:background="?android:attr/dividerVertical"
+ android:text="@string/no_media_label"/>
+
<View
android:layout_width="match_parent"
android:layout_height="1dp"
diff --git a/app/src/main/res/layout/filter_dialog_row.xml b/app/src/main/res/layout/filter_dialog_row.xml
index 5011812d9..914525387 100644
--- a/app/src/main/res/layout/filter_dialog_row.xml
+++ b/app/src/main/res/layout/filter_dialog_row.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<de.danoeh.antennapod.view.RecursiveRadioGroup
+<de.danoeh.antennapod.ui.common.RecursiveRadioGroup
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -58,4 +58,4 @@
android:layout_gravity="center_vertical"
android:checked="true" />
-</de.danoeh.antennapod.view.RecursiveRadioGroup>
+</de.danoeh.antennapod.ui.common.RecursiveRadioGroup>
diff --git a/app/src/main/res/layout/gpodnetauth_activity.xml b/app/src/main/res/layout/gpodnetauth_activity.xml
deleted file mode 100644
index c096c20cf..000000000
--- a/app/src/main/res/layout/gpodnetauth_activity.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-<ViewFlipper
- android:id="@+id/viewflipper"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
-</ScrollView> \ No newline at end of file
diff --git a/app/src/main/res/layout/gpodnetauth_credentials.xml b/app/src/main/res/layout/gpodnetauth_credentials.xml
index 895b0999c..291b98da3 100644
--- a/app/src/main/res/layout/gpodnetauth_credentials.xml
+++ b/app/src/main/res/layout/gpodnetauth_credentials.xml
@@ -1,96 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout
+<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="match_parent"
- android:padding="16dp">
-
- <ImageView
- android:id="@id/icon"
- android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/gpodder_icon" />
+ android:orientation="vertical">
- <TextView
- android:id="@id/txtvDescription"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/gpodnetauth_login_descr"
- android:layout_below="@id/icon"
- android:textSize="@dimen/text_size_medium"
- android:textColor="?android:attr/textColorPrimary"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginBottom="8dp">
- <EditText
- android:id="@+id/etxtUsername"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/username_label"
- android:layout_below="@id/txtvDescription"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:cursorVisible="true"
- android:maxLines="1"
- android:inputType="text"
- android:imeOptions="actionNext|flagNoFullscreen"
- android:nextFocusForward="@id/etxtPassword"/>
+ <ImageView
+ android:layout_width="64dp"
+ android:layout_height="64dp"
+ android:src="@drawable/gpodder_icon"/>
- <EditText
- android:id="@+id/etxtPassword"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/password_label"
- android:layout_below="@id/etxtUsername"
- android:inputType="textPassword"
- android:focusable="true"
- android:focusableInTouchMode="true"
- android:cursorVisible="true"
- android:imeOptions="actionGo|flagNoFullscreen"
- android:imeActionLabel="@string/gpodnetauth_login_butLabel"/>
+ <TextView
+ android:id="@+id/createAccountButton"
+ android:layout_width="0dp"
+ android:textAlignment="textEnd"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:textColor="?colorAccent"
+ android:layout_weight="1"
+ android:layout_gravity="center_vertical|end"
+ android:text="@string/create_account"/>
+ </LinearLayout>
- <Button
- android:id="@+id/butLogin"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/etxtPassword"
- android:layout_alignParentRight="true"
- android:layout_alignParentEnd="true"
- android:text="@string/gpodnetauth_login_butLabel"/>
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
- <TextView
- android:id="@+id/txtvError"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_below="@id/etxtPassword"
- android:layout_alignParentLeft="true"
- android:layout_alignParentStart="true"
- android:layout_toLeftOf="@id/butLogin"
- android:layout_toStartOf="@id/butLogin"
- android:textColor="@color/download_failed_red"
- android:textSize="@dimen/text_size_small"
- android:maxLines="2"
- android:ellipsize="end"
- android:gravity="center"
- android:layout_margin="16dp"
- tools:text="Error message"
- tools:background="@android:color/holo_green_dark" />
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/etxtUsername"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/username_label"
+ android:lines="1"
+ android:imeOptions="actionNext|flagNoFullscreen"/>
- <ProgressBar
- android:id="@+id/progBarLogin"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- android:layout_alignTop="@+id/butLogin"
- android:layout_toLeftOf="@+id/butLogin"
- android:layout_toStartOf="@+id/butLogin"/>
+ </com.google.android.material.textfield.TextInputLayout>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textSize="@dimen/text_size_medium"
- android:textColor="?android:attr/textColorPrimary"
- android:layout_marginTop="16dp"
- android:text="@string/gpodnetauth_login_register"
- android:autoLink="web"
- android:layout_below="@id/butLogin"/>
-</RelativeLayout> \ No newline at end of file
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/etxtPassword"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/password_label"
+ android:inputType="textPassword"
+ android:lines="1"
+ android:imeOptions="actionNext|flagNoFullscreen"
+ android:imeActionLabel="@string/gpodnetauth_login_butLabel"/>
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="end|center_vertical">
+
+ <TextView
+ android:id="@+id/credentialsError"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:textColor="@color/download_failed_red"
+ android:textSize="@dimen/text_size_small"
+ android:maxLines="2"
+ android:ellipsize="end"
+ android:gravity="center"
+ tools:text="Error message"
+ tools:background="@android:color/holo_green_dark"/>
+
+ <ProgressBar
+ android:id="@+id/progBarLogin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layout_gravity="right"/>
+
+ <Button
+ android:id="@+id/butLogin"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/gpodnetauth_login_butLabel"/>
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/gpodnetauth_device.xml b/app/src/main/res/layout/gpodnetauth_device.xml
index 7837121e1..656ba0889 100644
--- a/app/src/main/res/layout/gpodnetauth_device.xml
+++ b/app/src/main/res/layout/gpodnetauth_device.xml
@@ -1,114 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout
+<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="match_parent"
- android:padding="16dp">
-
- <TextView
- android:id="@+id/txtvTitle"
- android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/gpodnetauth_device_title"
- android:layout_alignParentTop="true"
- android:layout_marginBottom="16dp"
- style="@style/AntennaPod.TextView.Heading"/>
+ android:orientation="vertical">
- <TextView
- android:id="@+id/txtvDescription"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/gpodnetauth_device_descr"
- android:layout_below="@id/txtvTitle"
- android:textSize="@dimen/text_size_medium"
- android:textColor="?android:attr/textColorPrimary"/>
-
- <EditText
- android:id="@+id/etxtCaption"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:hint="@string/gpodnetauth_device_caption"
- android:layout_below="@id/txtvDescription"
- android:imeOptions="flagNoFullscreen"/>
+ <com.google.android.material.textfield.TextInputLayout
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
- <TextView
- android:id="@+id/txtvDeviceID"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/gpodnetauth_device_deviceID"
- android:textSize="@dimen/text_size_medium"
- android:layout_below="@id/etxtCaption"/>
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/deviceName"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/gpodnetauth_device_name"
+ android:lines="1"
+ android:imeOptions="actionNext|flagNoFullscreen"/>
- <EditText
- android:id="@+id/etxtDeviceID"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/txtvDeviceID"
- android:layout_alignParentRight="true"
- android:layout_alignParentEnd="true"
- android:imeOptions="flagNoFullscreen"/>
+ </com.google.android.material.textfield.TextInputLayout>
<Button
- android:id="@+id/butCreateNewDevice"
- android:layout_width="wrap_content"
+ android:id="@+id/createDeviceButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|end"
+ android:text="@string/gpodnetauth_create_device"/>
+
+ <TextView
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentRight="true"
- android:layout_alignParentEnd="true"
- android:layout_below="@id/etxtDeviceID"
- android:text="@string/gpodnetauth_device_butCreateNewDevice"/>
+ style="@style/AntennaPod.TextView.Heading"
+ android:layout_marginTop="16dp"
+ android:text="@string/gpodnetauth_existing_devices"/>
<TextView
- android:id="@+id/txtvError"
- android:layout_width="0dp"
+ android:id="@+id/deviceSelectError"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_alignParentStart="true"
- android:layout_below="@id/etxtDeviceID"
- android:layout_toLeftOf="@id/butCreateNewDevice"
- android:layout_toStartOf="@id/butCreateNewDevice"
android:textColor="@color/download_failed_red"
android:textSize="@dimen/text_size_small"
+ android:visibility="gone"
tools:text="Error message"
tools:background="@android:color/holo_green_dark" />
- <ProgressBar
- android:id="@+id/progbarCreateDevice"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignTop="@id/butCreateNewDevice"
- android:layout_toLeftOf="@id/butCreateNewDevice"
- android:layout_toStartOf="@id/butCreateNewDevice"
- android:textColor="@color/download_failed_red"
- android:textSize="@dimen/text_size_medium"
- android:visibility="gone"
- />
-
- <TextView
- android:id="@+id/txtvChooseExistingDevice"
+ <LinearLayout
+ android:id="@+id/devicesContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/gpodnetauth_device_chooseExistingDevice"
- android:layout_below="@id/butCreateNewDevice"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="@dimen/text_size_medium"
- android:layout_marginTop="32dp"/>
+ android:orientation="vertical" />
- <Button
- android:id="@+id/butChooseExistingDevice"
+ <ProgressBar
+ android:id="@+id/progbarCreateDevice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/gpodnetauth_device_butChoose"
- android:layout_below="@+id/spinnerChooseDevice"
- android:layout_alignParentRight="true"
- android:layout_alignParentEnd="true"/>
-
- <Spinner
- android:id="@+id/spinnerChooseDevice"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/txtvChooseExistingDevice"
- android:layout_alignParentLeft="true"
- android:layout_alignParentStart="true"/>
+ android:textColor="@color/download_failed_red"
+ android:visibility="gone" />
-</RelativeLayout> \ No newline at end of file
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/gpodnetauth_device_row.xml b/app/src/main/res/layout/gpodnetauth_device_row.xml
new file mode 100644
index 000000000..d39c00571
--- /dev/null
+++ b/app/src/main/res/layout/gpodnetauth_device_row.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp">
+
+ <Button
+ android:id="@+id/selectDeviceButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="?attr/materialButtonOutlinedStyle" />
+</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/gpodnetauth_dialog.xml b/app/src/main/res/layout/gpodnetauth_dialog.xml
new file mode 100644
index 000000000..a70b76a49
--- /dev/null
+++ b/app/src/main/res/layout/gpodnetauth_dialog.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"
+ android:clipToPadding="false">
+ <ViewFlipper
+ android:id="@+id/viewflipper"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inAnimation="@anim/slide_right_in"
+ android:outAnimation="@anim/slide_left_out">
+
+ <include layout="@layout/gpodnetauth_host" />
+ <include layout="@layout/gpodnetauth_credentials" />
+ <include layout="@layout/gpodnetauth_device" />
+ <include layout="@layout/gpodnetauth_finish" />
+
+ </ViewFlipper>
+</ScrollView> \ No newline at end of file
diff --git a/app/src/main/res/layout/gpodnetauth_finish.xml b/app/src/main/res/layout/gpodnetauth_finish.xml
index fdaa0d5d0..f0bcfd4dc 100644
--- a/app/src/main/res/layout/gpodnetauth_finish.xml
+++ b/app/src/main/res/layout/gpodnetauth_finish.xml
@@ -1,46 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="16dp">
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
<ImageView
android:id="@id/icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="64dp"
+ android:layout_height="64dp"
android:src="@drawable/gpodder_icon" />
<TextView
- android:id="@+id/txtvTitle"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/icon"
- android:text="@string/gpodnetauth_finish_title"
- style="@style/AntennaPod.TextView.Heading"/>
-
- <TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_finish_descr"
- android:layout_below="@id/txtvTitle"
- android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorPrimary" />
<Button
android:id="@+id/butSyncNow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_below="@id/txtvDescription"
- android:layout_marginTop="16dp"
+ android:layout_marginTop="8dp"
android:text="@string/gpodnetauth_finish_butsyncnow"/>
- <Button
- android:id="@+id/butGoMainscreen"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_below="@id/butSyncNow"
- android:text="@string/gpodnetauth_finish_butgomainscreen"/>
-
-</RelativeLayout> \ No newline at end of file
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/gpodnetauth_host.xml b/app/src/main/res/layout/gpodnetauth_host.xml
new file mode 100644
index 000000000..52c5fdb5d
--- /dev/null
+++ b/app/src/main/res/layout/gpodnetauth_host.xml
@@ -0,0 +1,50 @@
+<?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">
+
+ <RadioGroup
+ android:id="@+id/serverRadioGroup"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <RadioButton
+ android:id="@+id/officialServerRadio"
+ android:text="@string/gpodnetauth_server_official"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:checked="true"/>
+ <RadioButton
+ android:id="@+id/customServerRadio"
+ android:text="@string/gpodnetauth_server_custom"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </RadioGroup>
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:id="@+id/serverUrlTextInput"
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+ android:visibility="gone"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <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>
+
+ <Button
+ android:id="@+id/chooseHostButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right|end"
+ android:text="@string/gpodnetauth_select_server"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/onlinefeedview_activity.xml b/app/src/main/res/layout/onlinefeedview_activity.xml
index 88ffbb8b6..909d676f0 100644
--- a/app/src/main/res/layout/onlinefeedview_activity.xml
+++ b/app/src/main/res/layout/onlinefeedview_activity.xml
@@ -105,35 +105,55 @@
tools:text="Podcast author"/>
</RelativeLayout>
- <Spinner
- android:id="@+id/alternate_urls_spinner"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="8dp"
- android:layout_marginTop="8dp"
- android:padding="8dp"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="@dimen/text_size_micro"/>
-
- <Button
- android:id="@+id/subscribeButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_margin="16dp"
- android:focusable="false"
- android:text="@string/subscribe_label"/>
-
- <Button
- android:id="@+id/stopPreviewButton"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginLeft="16dp"
- android:layout_marginRight="16dp"
+ android:layout_marginStart="8dp"
+ android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
- android:focusable="false"
- android:text="@string/stop_preview"
- android:visibility="gone" />
+ android:orientation="vertical">
+
+ <Spinner
+ android:id="@+id/alternate_urls_spinner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:padding="8dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="@dimen/text_size_micro" />
+
+ <Button
+ android:id="@+id/subscribeButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:layout_marginTop="16dp"
+ android:layout_marginEnd="8dp"
+ android:focusable="false"
+ android:text="@string/subscribe_label" />
+
+ <CheckBox
+ android:id="@+id/autoDownloadCheckBox"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="left"
+ android:focusable="false"
+ android:checked="true"
+ android:text="@string/auto_download_label"
+ android:visibility="gone"
+ tools:visibility="visible" />
+
+ <Button
+ android:id="@+id/stopPreviewButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_margin="8dp"
+ android:focusable="false"
+ android:text="@string/stop_preview"
+ android:visibility="gone"
+ tools:visibility="visible" />
+
+ </LinearLayout>
<ListView
android:id="@+id/listView"
diff --git a/app/src/main/res/layout/quick_feed_discovery.xml b/app/src/main/res/layout/quick_feed_discovery.xml
index 0c55311e3..9ef3db180 100644
--- a/app/src/main/res/layout/quick_feed_discovery.xml
+++ b/app/src/main/res/layout/quick_feed_discovery.xml
@@ -23,7 +23,7 @@
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minHeight="0dp"
+ android:minHeight="48dp"
android:minWidth="0dp"
android:text="@string/discover_more"
style="@style/Widget.MaterialComponents.Button.TextButton"
@@ -34,7 +34,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
- <de.danoeh.antennapod.view.WrappingGridView
+ <de.danoeh.antennapod.ui.common.WrappingGridView
android:id="@+id/discover_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -85,6 +85,7 @@
</RelativeLayout>
<TextView
+ android:id="@+id/discover_powered_by_itunes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorTertiary"
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 e1c91f9d8..cb03b6677 100644
--- a/app/src/main/res/layout/quick_feed_discovery_item.xml
+++ b/app/src/main/res/layout/quick_feed_discovery_item.xml
@@ -7,7 +7,7 @@
android:padding="4dp"
android:clipToPadding="false">
- <de.danoeh.antennapod.view.SquareImageView
+ <de.danoeh.antennapod.ui.common.SquareImageView
android:id="@+id/discovery_cover"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/app/src/main/res/layout/searchlist_item_feed.xml b/app/src/main/res/layout/searchlist_item_feed.xml
index 607a3197f..c16911f99 100644
--- a/app/src/main/res/layout/searchlist_item_feed.xml
+++ b/app/src/main/res/layout/searchlist_item_feed.xml
@@ -7,7 +7,7 @@
android:padding="4dp"
android:clipToPadding="false">
- <de.danoeh.antennapod.view.SquareImageView
+ <de.danoeh.antennapod.ui.common.SquareImageView
android:id="@+id/discovery_cover"
android:layout_width="match_parent"
android:layout_height="96dp"
diff --git a/app/src/main/res/layout/secondary_action.xml b/app/src/main/res/layout/secondary_action.xml
index 73ca174a6..e5bff480e 100644
--- a/app/src/main/res/layout/secondary_action.xml
+++ b/app/src/main/res/layout/secondary_action.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginRight="12dp"
@@ -19,9 +20,10 @@
tools:ignore="ContentDescription"
tools:src="@sample/secondaryaction"/>
- <de.danoeh.antennapod.view.CircularProgressBar
+ <de.danoeh.antennapod.ui.common.CircularProgressBar
android:id="@+id/secondaryActionProgress"
android:layout_width="40dp"
android:layout_gravity="center"
- android:layout_height="40dp"/>
+ android:layout_height="40dp"
+ app:foregroundColor="?attr/action_icon_color"/>
</FrameLayout>
diff --git a/app/src/main/res/layout/subscription_item.xml b/app/src/main/res/layout/subscription_item.xml
index e0c821868..7fa738f12 100644
--- a/app/src/main/res/layout/subscription_item.xml
+++ b/app/src/main/res/layout/subscription_item.xml
@@ -8,7 +8,7 @@
android:layout_height="match_parent"
android:foreground="?attr/selectableItemBackground">
- <de.danoeh.antennapod.view.SquareImageView
+ <de.danoeh.antennapod.ui.common.SquareImageView
android:id="@+id/imgvCover"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
diff --git a/app/src/main/res/layout/time_dialog.xml b/app/src/main/res/layout/time_dialog.xml
index 6523bb1be..6b6ab3195 100644
--- a/app/src/main/res/layout/time_dialog.xml
+++ b/app/src/main/res/layout/time_dialog.xml
@@ -2,6 +2,7 @@
<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">
@@ -52,10 +53,11 @@
<TextView
android:text="00:00:00"
android:layout_gravity="center"
+ android:gravity="center"
android:textSize="32sp"
android:textColor="?android:attr/textColorPrimary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:id="@+id/time"/>
<Button
@@ -64,6 +66,45 @@
android:layout_height="wrap_content"
android:id="@+id/disableSleeptimerButton"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <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" />
+
+ <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" />
+
+ <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" />
+
+ </LinearLayout>
+
</LinearLayout>
@@ -93,4 +134,4 @@
</LinearLayout>
-</LinearLayout> \ No newline at end of file
+</LinearLayout>
diff --git a/app/src/main/res/layout/videoplayer_activity.xml b/app/src/main/res/layout/videoplayer_activity.xml
index c978a1e4d..e0632ef41 100644
--- a/app/src/main/res/layout/videoplayer_activity.xml
+++ b/app/src/main/res/layout/videoplayer_activity.xml
@@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ xmlns:tools="http://schemas.android.com/tools"
android:background="@color/black"
android:orientation="vertical"
android:id="@+id/videoframe">
@@ -75,6 +76,33 @@
android:layout_gravity="bottom|center"
android:orientation="vertical">
+ <androidx.cardview.widget.CardView
+ android:id="@+id/cardViewSeek"
+ android:alpha="0"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="12dp"
+ android:layout_gravity="center"
+ app:cardCornerRadius="8dp"
+ app:cardBackgroundColor="?attr/seek_background"
+ app:cardElevation="0dp"
+ tools:alpha="1">
+
+ <TextView
+ android:id="@+id/txtvSeek"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="24dp"
+ android:paddingTop="4dp"
+ android:paddingRight="24dp"
+ android:paddingBottom="4dp"
+ android:textColor="@color/white"
+ android:textSize="24sp"
+ tools:text="1:06:29" />
+
+ </androidx.cardview.widget.CardView>
+
<RelativeLayout
android:id="@+id/timecontrol"
android:layout_width="match_parent"
diff --git a/app/src/main/res/menu/bug_report_options.xml b/app/src/main/res/menu/bug_report_options.xml
new file mode 100644
index 000000000..62963210c
--- /dev/null
+++ b/app/src/main/res/menu/bug_report_options.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <item android:id="@+id/export_logcat"
+ android:title="@string/export_logs_menu_title" />
+
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/feedinfo.xml b/app/src/main/res/menu/feedinfo.xml
index b1daf1f36..a5fbe0c20 100644
--- a/app/src/main/res/menu/feedinfo.xml
+++ b/app/src/main/res/menu/feedinfo.xml
@@ -8,17 +8,25 @@
android:title="@string/visit_website_label"
android:visible="true"/>
<item
- android:id="@+id/share_link_item"
- custom:showAsAction="collapseActionView"
- android:title="@string/share_website_url_label"/>
- <item
- android:id="@+id/share_download_url_item"
- custom:showAsAction="collapseActionView"
- android:title="@string/share_feed_url_label"/>
+ android:id="@+id/share_parent"
+ custom:showAsAction="ifRoom"
+ android:title="@string/share_label_with_ellipses"
+ android:icon="?attr/ic_share"
+ android:visible="true">
+ <menu android:id="@+id/share_submenu">
+ <item
+ android:id="@+id/share_link_item"
+ custom:showAsAction="collapseActionView"
+ android:title="@string/share_website_url_label"/>
+ <item
+ android:id="@+id/share_download_url_item"
+ custom:showAsAction="collapseActionView"
+ android:title="@string/share_feed_url_label"/>
+ </menu>
+ </item>
<item
android:id="@+id/reconnect_local_folder"
custom:showAsAction="collapseActionView"
android:title="@string/reconnect_local_folder"
android:visible="false" />
-
-</menu>
+</menu> \ No newline at end of file
diff --git a/app/src/main/res/menu/subscriptions.xml b/app/src/main/res/menu/subscriptions.xml
index b7dc95299..99acc4bb6 100644
--- a/app/src/main/res/menu/subscriptions.xml
+++ b/app/src/main/res/menu/subscriptions.xml
@@ -23,22 +23,21 @@
android:title="@string/subscription_num_columns"
custom:showAsAction="never">
<menu>
- <item
- android:id="@+id/subscription_num_columns_2"
- android:checkable="true"
- android:title="2"/>
- <item
- android:id="@+id/subscription_num_columns_3"
- android:checkable="true"
- android:title="3"/>
- <item
- android:id="@+id/subscription_num_columns_4"
- android:checkable="true"
- android:title="4"/>
- <item
- android:id="@+id/subscription_num_columns_5"
- android:checkable="true"
- android:title="5"/>
+ <group
+ android:checkableBehavior="single">
+ <item
+ android:id="@+id/subscription_num_columns_2"
+ android:title="2"/>
+ <item
+ android:id="@+id/subscription_num_columns_3"
+ android:title="3"/>
+ <item
+ android:id="@+id/subscription_num_columns_4"
+ android:title="4"/>
+ <item
+ android:id="@+id/subscription_num_columns_5"
+ android:title="5"/>
+ </group>
</menu>
</item>
</menu>
diff --git a/app/src/main/res/xml/actions.xml b/app/src/main/res/xml/actions.xml
new file mode 100644
index 000000000..20dc3dc9b
--- /dev/null
+++ b/app/src/main/res/xml/actions.xml
@@ -0,0 +1,25 @@
+<?xml version ="1.0" encoding ="utf-8"?>
+<actions>
+ <action intentName="actions.intent.OPEN_APP_FEATURE">
+ <fulfillment urlTemplate="https://antennapod.org/deeplink/main{?page}">
+ <parameter-mapping intentParameter="feature" urlParameter="page" />
+ </fulfillment>
+ <parameter name="feature">
+ <entity-set-reference entitySetId="featureEntitySet" />
+ </parameter>
+ </action>
+
+ <action intentName="actions.intent.GET_THING">
+ <fulfillment urlTemplate="https://antennapod.org/deeplink/search{?query}">
+ <parameter-mapping intentParameter="thing.name" urlParameter="query"/>
+ </fulfillment>
+ </action>
+
+ <entity-set entitySetId="featureEntitySet">
+ <entity identifier="QUEUE" name="@string/queue_label" />
+ <entity identifier="EPISODES" name="@string/episodes_label" />
+ <entity identifier="DOWNLOADS" name="@string/downloads_label" />
+ <entity identifier="SUBSCRIPTIONS" name="@string/subscriptions_label" />
+ <entity identifier="HISTORY" name="@string/playback_history_label" />
+ </entity-set>
+</actions>
diff --git a/app/src/main/res/xml/feed_settings.xml b/app/src/main/res/xml/feed_settings.xml
index 9d5ed5e8b..13288fbda 100644
--- a/app/src/main/res/xml/feed_settings.xml
+++ b/app/src/main/res/xml/feed_settings.xml
@@ -9,6 +9,14 @@
android:title="@string/keep_updated"
android:summary="@string/keep_updated_summary"/>
+ <SwitchPreferenceCompat
+ android:key="episodeNotification"
+ android:defaultValue="false"
+ android:dependency="keepUpdated"
+ android:icon="?attr/ic_notifications"
+ android:title="@string/episode_notification"
+ android:summary="@string/episode_notification_summary"/>
+
<Preference
android:key="authentication"
android:icon="?attr/ic_key"
diff --git a/app/src/main/res/xml/player_widget_info.xml b/app/src/main/res/xml/player_widget_info.xml
index 79cdd4a69..803cc89ed 100644
--- a/app/src/main/res/xml/player_widget_info.xml
+++ b/app/src/main/res/xml/player_widget_info.xml
@@ -8,5 +8,4 @@
android:minWidth="250dp"
android:minResizeWidth="40dp"
android:configure="de.danoeh.antennapod.activity.WidgetConfigActivity">
-
-</appwidget-provider> \ No newline at end of file
+</appwidget-provider>
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 1630dc2f9..805dff47d 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -53,8 +53,8 @@
android:key="project"
android:title="@string/project_pref">
<Preference
- android:key="prefFaq"
- android:title="@string/pref_faq"
+ android:key="prefDocumentation"
+ android:title="@string/documentation_support"
android:icon="?attr/ic_questionmark" />
<Preference
android:key="prefViewForum"
diff --git a/app/src/main/res/xml/preferences_about.xml b/app/src/main/res/xml/preferences_about.xml
index 475a1152f..f56b7f2ac 100644
--- a/app/src/main/res/xml/preferences_about.xml
+++ b/app/src/main/res/xml/preferences_about.xml
@@ -17,7 +17,7 @@
<Preference
android:key="about_privacy_policy"
android:icon="?attr/ic_questionmark"
- android:summary="https://antennapod.org/privacy.html"
+ android:summary="www.antennapod.org/privacy"
android:title="@string/privacy_policy"/>
<Preference
android:key="about_licenses"
diff --git a/app/src/main/res/xml/preferences_gpodder.xml b/app/src/main/res/xml/preferences_gpodder.xml
index 7bddbf245..a210b8e11 100644
--- a/app/src/main/res/xml/preferences_gpodder.xml
+++ b/app/src/main/res/xml/preferences_gpodder.xml
@@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
-
- <PreferenceScreen
+ <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">
- <intent android:action=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
- </PreferenceScreen>
+ android:summary="@string/pref_gpodnet_authenticate_sum"/>
<Preference
android:key="pref_gpodnet_setlogin_information"
android:title="@string/pref_gpodnet_setlogin_information_title"
@@ -23,8 +24,5 @@
<Preference
android:key="pref_gpodnet_logout"
android:title="@string/pref_gpodnet_logout_title"/>
- <Preference
- android:key="pref_gpodnet_hostname"
- android:title="@string/pref_gpodnet_sethostname_title"/>
</PreferenceScreen>
diff --git a/app/src/main/res/xml/preferences_notifications.xml b/app/src/main/res/xml/preferences_notifications.xml
index 3aa907ca8..237ea5c99 100644
--- a/app/src/main/res/xml/preferences_notifications.xml
+++ b/app/src/main/res/xml/preferences_notifications.xml
@@ -2,21 +2,28 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
- <SwitchPreferenceCompat
- android:defaultValue="true"
- android:enabled="true"
- android:key="prefShowDownloadReport"
- android:summary="@string/pref_showDownloadReport_sum"
- android:title="@string/pref_showDownloadReport_title" />
- <SwitchPreferenceCompat
- android:defaultValue="false"
- android:enabled="true"
- android:key="prefShowAutoDownloadReport"
- android:summary="@string/pref_showAutoDownloadReport_sum"
- android:title="@string/pref_showAutoDownloadReport_title" />
- <SwitchPreferenceCompat
- android:defaultValue="true"
- android:key="pref_gpodnet_notifications"
- android:summary="@string/pref_gpodnet_notifications_sum"
- android:title="@string/pref_gpodnet_notifications_title" />
+ <PreferenceCategory
+ android:title="@string/notification_group_news">
+ <SwitchPreferenceCompat
+ android:defaultValue="false"
+ android:enabled="true"
+ android:key="prefShowAutoDownloadReport"
+ android:summary="@string/notification_channel_episode_auto_download"
+ android:title="@string/notification_channel_auto_download" />
+ </PreferenceCategory>
+
+ <PreferenceCategory
+ android:title="@string/notification_group_errors">
+ <SwitchPreferenceCompat
+ android:defaultValue="true"
+ android:enabled="true"
+ android:key="prefShowDownloadReport"
+ android:summary="@string/notification_channel_download_error_description"
+ android:title="@string/notification_channel_download_error" />
+ <SwitchPreferenceCompat
+ android:defaultValue="true"
+ android:key="pref_gpodnet_notifications"
+ android:summary="@string/notification_channel_sync_error_description"
+ android:title="@string/notification_channel_sync_error" />
+ </PreferenceCategory>
</PreferenceScreen> \ No newline at end of file
diff --git a/app/src/main/res/xml/preferences_playback.xml b/app/src/main/res/xml/preferences_playback.xml
index d2999c59d..2be8492eb 100644
--- a/app/src/main/res/xml/preferences_playback.xml
+++ b/app/src/main/res/xml/preferences_playback.xml
@@ -44,18 +44,6 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/playback_control">
- <SwitchPreferenceCompat
- android:defaultValue="false"
- android:enabled="true"
- android:key="prefHardwareForwardButtonSkips"
- android:summary="@string/pref_hardwareForwardButtonSkips_sum"
- android:title="@string/pref_hardwareForwardButtonSkips_title"/>
- <SwitchPreferenceCompat
- android:defaultValue="false"
- android:enabled="true"
- android:key="prefHardwarePreviousButtonRestarts"
- android:summary="@string/pref_hardwarePreviousButtonRestarts_sum"
- android:title="@string/pref_hardwarePreviousButtonRestarts_title"/>
<Preference
android:key="prefPlaybackFastForwardDeltaLauncher"
android:summary="@string/pref_fast_forward_sum"
@@ -80,6 +68,23 @@
android:title="@string/pref_stream_over_download_title"/>
</PreferenceCategory>
+ <PreferenceCategory android:title="@string/reassign_hardware_buttons">
+ <ListPreference
+ android:defaultValue="@string/keycode_media_fast_forward"
+ android:entries="@array/button_action_options"
+ android:entryValues="@array/button_action_values"
+ android:key="prefHardwareForwardButton"
+ android:title="@string/pref_hardware_forward_button_title"
+ android:summary="@string/pref_hardware_forward_button_summary"/>
+ <ListPreference
+ android:defaultValue="@string/keycode_media_rewind"
+ android:entries="@array/button_action_options"
+ android:entryValues="@array/button_action_values"
+ android:key="prefHardwarePreviousButton"
+ android:title="@string/pref_hardware_previous_button_title"
+ android:summary="@string/pref_hardware_previous_button_summary"/>
+ </PreferenceCategory>
+
<PreferenceCategory android:title="@string/queue_label">
<SwitchPreferenceCompat
android:defaultValue="true"
diff --git a/app/src/main/res/xml/preferences_user_interface.xml b/app/src/main/res/xml/preferences_user_interface.xml
index 203e14d42..f8e80cdff 100644
--- a/app/src/main/res/xml/preferences_user_interface.xml
+++ b/app/src/main/res/xml/preferences_user_interface.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android">
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:search="http://schemas.android.com/apk/com.bytehamster.lib.preferencesearch">
<PreferenceCategory android:title="@string/appearance">
<ListPreference
@@ -20,6 +21,12 @@
android:summary="@string/pref_episode_cover_summary"
android:defaultValue="true"
android:enabled="true"/>
+ <SwitchPreferenceCompat
+ android:title="@string/pref_show_remain_time_title"
+ android:key="showTimeLeft"
+ android:summary="@string/pref_show_remain_time_summary"
+ android:defaultValue="false"
+ android:enabled="true"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/subscriptions_label">
<Preference
@@ -44,7 +51,8 @@
android:enabled="true"
android:key="prefExpandNotify"
android:summary="@string/pref_expandNotify_sum"
- android:title="@string/pref_expandNotify_title"/>
+ android:title="@string/pref_expandNotify_title"
+ search:ignore="true"/>
<SwitchPreferenceCompat
android:defaultValue="true"
android:enabled="true"
diff --git a/app/src/play/java/de/danoeh/antennapod/dialog/RatingDialog.java b/app/src/play/java/de/danoeh/antennapod/dialog/RatingDialog.java
index cfadf0772..66072e2fa 100644
--- a/app/src/play/java/de/danoeh/antennapod/dialog/RatingDialog.java
+++ b/app/src/play/java/de/danoeh/antennapod/dialog/RatingDialog.java
@@ -7,7 +7,6 @@ import android.content.SharedPreferences;
import androidx.annotation.VisibleForTesting;
import android.util.Log;
-import android.widget.Toast;
import java.lang.ref.WeakReference;
import java.util.concurrent.TimeUnit;