diff options
Diffstat (limited to 'app')
132 files changed, 1412 insertions, 3771 deletions
diff --git a/app/build.gradle b/app/build.gradle index 2880f980f..ef8e850b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,8 +22,8 @@ android { // "1.2.3-SNAPSHOT" -> 1020300 // "1.2.3-RC4" -> 1020304 // "1.2.3" -> 1020395 - versionCode 2000395 - versionName "2.0.3" + versionCode 2010295 + versionName "2.1.2" multiDexEnabled false vectorDrawables.useSupportLibrary true 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/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/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/storage/AutoDownloadTest.java b/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java index 5396b218d..1d2e3d9e8 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/AutoDownloadTest.java @@ -4,14 +4,12 @@ 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.util.playback.PlaybackServiceStarter; import de.test.antennapod.EspressoTestUtils; import de.test.antennapod.ui.UITestUtils; @@ -32,7 +30,7 @@ public class AutoDownloadTest { private Context context; private UITestUtils stubFeedsServer; - private DBTasksCallbacks dbTasksCallbacksOrig; + private AutomaticDownloadAlgorithm automaticDownloadAlgorithmOrig; @Before public void setUp() throws Exception { @@ -41,7 +39,7 @@ public class AutoDownloadTest { stubFeedsServer = new UITestUtils(context); stubFeedsServer.setup(); - dbTasksCallbacksOrig = ClientConfig.dbTasksCallbacks; + automaticDownloadAlgorithmOrig = ClientConfig.automaticDownloadAlgorithm; EspressoTestUtils.clearPreferences(); EspressoTestUtils.clearDatabase(); @@ -50,7 +48,7 @@ public class AutoDownloadTest { @After public void tearDown() throws Exception { - ClientConfig.dbTasksCallbacks = dbTasksCallbacksOrig; + ClientConfig.automaticDownloadAlgorithm = automaticDownloadAlgorithmOrig; EspressoTestUtils.tryKillPlaybackService(); stubFeedsServer.tearDown(); } @@ -79,7 +77,7 @@ public class AutoDownloadTest { // Setup: enable automatic download // it is not needed, as the actual automatic download is stubbed. StubDownloadAlgorithm stubDownloadAlgorithm = new StubDownloadAlgorithm(); - useDownloadAlgorithm(stubDownloadAlgorithm); + ClientConfig.automaticDownloadAlgorithm = stubDownloadAlgorithm; // Actual test // Play the first one in the queue @@ -113,20 +111,6 @@ 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 long currentlyPlaying = -1; diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java index 6c36da13e..339d3cea9 100644 --- a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java +++ b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java @@ -11,7 +11,6 @@ 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; 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/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/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..3cdb09605 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java @@ -43,9 +43,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 { 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..904e17ebf 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/SpeedChangeTest.java @@ -14,7 +14,6 @@ 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.test.antennapod.EspressoTestUtils; import de.test.antennapod.IgnoreOnCi; @@ -37,8 +36,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. 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/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fb205b1c3..b4a2c52a3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -189,15 +189,6 @@ <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="de.danoeh.antennapod.activity.MainActivity"/> - <intent-filter> - <action android:name="android.intent.action.VIEW"/> - - <category android:name="android.intent.category.DEFAULT"/> - <category android:name="android.intent.category.BROWSABLE"/> - - <data android:scheme="file"/> - <data android:mimeType="video/*"/> - </intent-filter> </activity> <activity @@ -292,19 +283,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"/> diff --git a/app/src/main/assets/developers.csv b/app/src/main/assets/developers.csv index cff15ee38..a2c54723d 100644 --- a/app/src/main/assets/developers.csv +++ b/app/src/main/assets/developers.csv @@ -1,35 +1,36 @@ ByteHamster;5811634;Maintainer danieloeh;968613;Original creator of AntennaPod (retired) -mfietz;6860662;Maintainer +mfietz;6860662;Maintainer (retired) TomHennen;5216560;Maintainer (retired) orionlee;250644;Contributor domingos86;9538859;Contributor +tonytamsf;149837;Contributor andersonvom;69922;Contributor +damoasda;46045854;Contributor TacoTheDank;32376686;Contributor shortspider;5712543;Contributor ebraminio;833473;Contributor +asdoi;36813904;Contributor spacecowboy;223655;Contributor patheticpat;16046;Contributor -tonytamsf;149837;Contributor brad;1614;Contributor Cj-Malone;10121513;Contributor maxbechtold;9162198;Contributor -asdoi;36813904;Contributor gaul;848247;Contributor qkolj;6667105;Contributor +keunes;11229646;Maintainer pachecosf;46357909;Contributor gerardolgvr;20119298;Contributor bws9000;262625;Contributor ahangarha;11241315;Contributor -damoasda;46045854;Contributor hannesa2;3314607;Contributor -keunes;11229646;Contributor rharriso;570910;Contributor xgouchet;818706;Contributor sevenmaster;12869538;Contributor TheRealFalcon;153674;Contributor -jas14;569991;Contributor +Slinger;75751;Contributor johnjohndoe;144518;Contributor +jas14;569991;Contributor udif;809640;Contributor malockin;12814657;Contributor dirkmueller;1029152;Contributor @@ -49,6 +50,7 @@ deandreamatias;21011641;Contributor MeirAtIMDDE;4421079;Contributor egsavage;126165;Contributor ligi;111600;Contributor +Xeitor;8825715;Contributor dreiss;4121;Contributor liesen;26872;Contributor nereocystis;2257107;Contributor @@ -70,30 +72,43 @@ SosoTughushi;19908097;Contributor fabolhak;20029691;Contributor archibishop;36948493;Contributor alifeflow;24603829;Contributor +avirajrsingh;69088913;Contributor toggles;14695;Contributor matdb;48329535;Contributor +damlayildiz;56313500;Contributor kingargyle;177042;Contributor dsmith47;14109426;Contributor hannesaa2;18496079;Contributor jhunnius;9149031;Contributor ShadowIce;59123;Contributor +Niffler;8172446;Contributor raghulj;57007;Contributor raghulrm;5362986;Contributor mamehacker;16738348;Contributor skitt;2128935;Contributor wseemann;2296196;Contributor +markamaze;17114678;Contributor +mohitshah3111999;42018918;Contributor +moralesg;14352147;Contributor mr-intj;6268767;Contributor tuxayo;2678215;Contributor schlch;56929215;Contributor alimemonzx;44647595;Contributor +dev-darrell;52300159;Contributor +jmdouglas;10855634;Contributor olivoto;15932680;Contributor +PtilopsisLeucotis;54054883;Contributor +abhinavg1997;60095795;Contributor alanorth;191754;Contributor alexte;7724992;Contributor andrey-krutov;1488973;Contributor arantius;84729;Contributor +BoJacobs;25435640;Contributor +chetan882777;36985543;Contributor chrissicool;232590;Contributor cszucko;1810383;Contributor CWftw;1498303;Contributor +connectety;26038710;Contributor danielm5;66779;Contributor ariedov;958646;Contributor brettle;118192;Contributor @@ -107,17 +122,21 @@ Kaligule;3586246;Contributor kvithayathil;1056073;Contributor luiscruz;1080714;Contributor mlasson;5814258;Contributor +schwedenmut;9077622;Contributor M-arcel;56698158;Contributor msoose;30473690;Contributor mo;7117;Contributor mdeveloper20;2319126;Contributor -Slinger;75751;Contributor mschuetz;108637;Contributor +max-wittig;6639323;Contributor MolarAmbiguity;10541979;Contributor mounirlamouri;573590;Contributor +nikhil097;35090769;Contributor +panoreak;25068506;Contributor +patrickjkennedy;8617261;Contributor ortylp;470439;Contributor -PtilopsisLeucotis;54054883;Contributor ramzan;55637406;Contributor +iamrichR;44210678;Contributor SamWhited;512573;Contributor selivan;1208989;Contributor sonnayasomnambula;7716779;Contributor @@ -126,13 +145,17 @@ shantanahardy;26757164;Contributor danners;116551;Contributor corecode;177979;Contributor vimsick;20211590;Contributor +lyallemma;25173082;Contributor edent;837136;Contributor atrus6;357881;Contributor +heyyviv;56256802;Contributor waylife;3348620;Contributor amhokies;3124968;Contributor andrewc1;19559401;Contributor axq;5077221;Contributor +binarytoto;75904760;Contributor +chrk2205;44704035;Contributor fossterer;4236021;Contributor -jmdouglas;10855634;Contributor lightonflux;1377943;Contributor minusf;3632883;Contributor +zawad2221;32180355;Contributor diff --git a/app/src/main/assets/licenses.xml b/app/src/main/assets/licenses.xml index 4f0255182..b6e12cf54 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" 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/assets/translators.csv b/app/src/main/assets/translators.csv index a7d289963..fddad789b 100644 --- a/app/src/main/assets/translators.csv +++ b/app/src/main/assets/translators.csv @@ -1,45 +1,45 @@ -Arabic;abuzar3.khalid, keunes, nabilMaghura, rex07 +Arabic;abuzar3.khalid, badarotti, keunes, nabilMaghura, rex07, shubbar Asturian (ast_ES);enolp Basque;gaztainalde, keunes, Osoitz, pospolos Breton;Belvar, keunes Bulgarian;keunes, solusitor -Catalan;carles.llacer, dvd1985, exort12, javiercoll, keunes, Kintu, lambdani, marcmetallextrem, xc70 -Chinese (zh_CN);brnme, cyril3, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, jhxie, jxj2zzz79pfp9bpo, keunes, kyleehee, molisiye, owen8877, RainSlide, Sak94664, spice2wolf, tupunco, wongsyrone, yangyang, yiqiok +Catalan;carles.llacer, dvd1985, exort12, IvanAmarante, javiercoll, keunes, Kintu, lambdani, marcmetallextrem, xc70 +Chinese (zh_CN);brnme, cyril3, Felix2yu, gaohongyuan, Guaidaodl, Huck0, iconteral, jhxie, jxj2zzz79pfp9bpo, keunes, kyleehee, molisiye, owen8877, RainSlide, RangerNJU, Sak94664, spice2wolf, tupunco, wongsyrone, yangyang, yiqiok Chinese (zh_TW);bobchao, ijliao, keunes, mapobi, pggdt, ymhuang0808 Czech (cs_CZ);anotheranonymoususer, elich, Hanzmeister, svetlemodry, Thomaash -Danish;jhertel, keunes, SebastianKiwiDk, twikedk +Danish;JFreak, jhertel, keunes, SebastianKiwiDk, twikedk Dutch;e2jk, keunes, rwv, Vistaus Estonian;Eraser, keunes, mahfiaz Finnish;Ban3, keunes, Sahtor French;ChaoticMind, clombion, Cornegidouille, e2jk, keunes, lacouture, LouFex, Matth78, Poussinou, sterylmreep Galician;antiparvos, pikamoku, Raichely -German;ByteHamster, ceving, dadosch, DerSilly, elkangaroo, enz, f_grubm, finsterwalder, HolgerJeromin, kalei, keunes, mfietz, Quiss42, repat, ypid +German;_Er, ByteHamster, ceving, dadosch, DerSilly, elkangaroo, enz, f_grubm, finsterwalder, hbilke, HolgerJeromin, JoeMcFly, kalei, keunes, mfietz, pudeeh, Quiss42, repat, tomte, tweimer, Willhelm, ypid Modern Greek (1453-);AnimaRain, antonist, keunes, pavlosv Hebrew (he_IL);amir.dafnyman, E1i9, mongoose4004, pinkasey, rellieberman, Yaron -Hindi (hi_IN);keunes, purple.coder, siddhusengar -Hungarian;hurrikan, keunes, lna91, marthynw, meskobalazs, naren93 +Hindi (hi_IN);keunes, purple.coder, siddhusengar, thelazyoxymoron +Hu;hurrikan, keunes, lna91, marthynw, meskobalazs, naren93 Icelandic;keunes, marthjod Indonesian;dbrw, keunes, levirs565 -Italian (it_IT);aalex70, allin, Bonnee, dontknowcris, giuseppep, Guybrush88, ilmanzo, keunes, m.chinni, marco_pag, neonsoftware, niccord, theloca95 +Italian (it_IT);aalex70, allin, alvami, Bonnee, dontknowcris, giuseppep, Guybrush88, ilmanzo, keunes, m.chinni, marco_pag, neonsoftware, niccord, salorock, theloca95 Japanese;keunes, KotaKato, Naofumi, sh3llc4t, TranslatorG Kannada (kn_IN);chiraag.nataraj, keunes, thejeshgn -Korean;changwoo, keunes, libliboom +Ko;changwoo, keunes, libliboom Lithuanian;keunes, naglis Macedonian;krisfremen Malayalam;joice, keunes, rashivkp -Norwegian Bokmål (nb_NO);abstrakct, bablecopherye, corkie, heraldo, jakobkg, keunes, kongk, sevenmaster, timbast -Persian;ahangarha, danialbehzadi, ebraminio, F7D, hamidrezabayat76, keunes, sinamoghaddas -Polish (pl_PL);hiro2020, Iwangelion, keunes, lomapur, mandlus, maniexx, Mephistofeles, shark103, tyle +Norwegian Bokmål (nb_NO);abstrakct, ahysing, bablecopherye, corkie, forteller, heraldo, jakobkg, keunes, kongk, sevenmaster, timbast +Persian;ahangarha, danialbehzadi, ebadi, ebraminio, F7D, hamidrezabayat76, keunes, sinamoghaddas +Polish (pl_PL);befeleme, hiro2020, Iwangelion, keunes, lomapur, mandlus, maniexx, Mephistofeles, shark103, tyle Portuguese;emansije, keunes, smarquespt Portuguese (pt_BR);alexupits, alysonborges, andersonvom, arua, caioau, carlo_valente, castrors, edman, keunes, lipefire, mbaltar, olivoto, rogervezaro, RubeensVinicius, SamWilliam Romanian (ro_RO);corneliu.e, fuzzmz, keunes, ralienpp -Russian (ru_RU);btimofeev, Duke_Raven, gammja, homocomputeris, IgorPolyakov, keunes, mercutiy, null, overmind88, Platun0v, PtilopsisLeucotis, s.chebotar, un_logic, Vladryyu, whereisthetea -Slovak;ati3, keunes, tiborepcek +Russian (ru_RU);ashed, btimofeev, Duke_Raven, gammja, homocomputeris, IgorPolyakov, keunes, mercutiy, null, overmind88, Platun0v, PtilopsisLeucotis, s.chebotar, un_logic, Vladryyu, whereisthetea +Slovak;ati3, keunes, marulinko, tiborepcek Slovenian (sl_SI);keunes, panter23 -Spanish;AleksSyntek, andersonvom, andrespelaezp, deandreamatias, dvd1985, Fitoschido, frandavid100, hard_ware, javiercoll, keunes, LatinSuD, leogrignafini, tres.14159, vfmatzkin, wakutiteo +Spanish;AleksSyntek, andersonvom, andrespelaezp, deandreamatias, dvd1985, elojodepajaro, Fitoschido, frandavid100, hard_ware, javiercoll, keunes, LatinSuD, leogrignafini, rafael.osuna, tres.14159, vfmatzkin, wakutiteo Swahili (macrolanguage);keunes, kmtra Swedish (sv_SE);bpnilsson, keunes, nilso, TwoD Telugu;keunes, veeven -Turkish;brsata, Erdy, keunes, overbite, Slsdem -Ukrainian (uk_UA);IndibidAbulya, keunes, older, paul_sm, sergiyr, zhenya97 +Turkish;AhmedDuran, brsata, Erdy, keunes, overbite, Slsdem +Ukrainian (uk_UA);keunes, older, paul_sm, sergiyr, zhenya97 Vietnamese;abnvolk, keunes, ppanhh 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..4900f0e27 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/DownloadAuthenticationActivity.java @@ -1,62 +1,54 @@ 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 androidx.appcompat.app.AppCompatActivity; -import android.widget.Button; +import android.text.TextUtils; import android.widget.EditText; import android.widget.TextView; - -import org.apache.commons.lang3.Validate; - +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; 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 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; + private DownloadRequest request; @Override protected void onCreate(Bundle savedInstanceState) { setTheme(UserPreferences.getNoTitleTheme()); 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); + request = getIntent().getParcelableExtra(ARG_DOWNLOAD_REQUEST); + TextView txtvDescription = findViewById(R.id.txtvDescription); String newDescription = txtvDescription.getText() + ":\n\n" + request.getTitle(); txtvDescription.setText(newDescription); @@ -65,28 +57,42 @@ public class DownloadAuthenticationActivity extends AppCompatActivity { 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); - } - finish(); - }); + findViewById(R.id.butConfirm).setOnClickListener(v -> + Completable.fromAction(this::updatePassword) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(() -> { + DownloadRequester.getInstance().download(DownloadAuthenticationActivity.this, request); + finish(); + })); - butCancel.setOnClickListener(v -> { + findViewById(R.id.butCancel).setOnClickListener(v -> { setResult(Activity.RESULT_CANCELED); finish(); }); } + private void updatePassword() { + String username = etxtUsername.getText().toString(); + String password = etxtPassword.getText().toString(); + 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); + } + } + } + } + @Override protected void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); 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 d39edb9b2..d1716e009 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MainActivity.java @@ -16,6 +16,7 @@ import android.view.KeyEvent; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import android.widget.FrameLayout; import android.widget.Toast; import androidx.annotation.NonNull; @@ -542,6 +543,11 @@ public class MainActivity extends CastEnabledActivity { //Hardware keyboard support @Override public boolean onKeyUp(int keyCode, KeyEvent event) { + View currentFocus = getCurrentFocus(); + if (currentFocus instanceof EditText) { + return super.onKeyUp(keyCode, event); + } + AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); Integer customKeyCode = null; 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..c1d921f8c 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,9 @@ 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 +16,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 +26,13 @@ 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.core.app.ActivityOptionsCompat; -import androidx.core.content.ContextCompat; 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 +44,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; @@ -74,8 +65,6 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements 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; @@ -664,50 +653,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 2fd537159..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; @@ -13,7 +14,6 @@ import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.UiThread; @@ -88,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; @@ -446,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 -> { @@ -512,10 +520,17 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (didPressSubscribe) { didPressSubscribe = false; if (UserPreferences.isEnableAutodownload()) { + boolean autoDownload = viewBinding.autoDownloadCheckBox.isChecked(); + Feed feed1 = DBReader.getFeed(getFeedId(feed)); FeedPreferences feedPreferences = feed1.getPreferences(); - feedPreferences.setAutoDownload(viewBinding.autoDownloadCheckBox.isChecked()); + 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(); } 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 2d4510e8f..75198c016 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java @@ -12,12 +12,11 @@ import android.view.KeyEvent; import android.view.animation.AlphaAnimation; import android.view.animation.AnimationSet; 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; @@ -37,7 +36,6 @@ 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; @@ -88,9 +86,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; @@ -489,6 +485,11 @@ public class VideoplayerActivity extends MediaplayerActivity { //Hardware keyboard support @Override public boolean onKeyUp(int keyCode, KeyEvent event) { + View currentFocus = getCurrentFocus(); + if (currentFocus instanceof EditText) { + return super.onKeyUp(keyCode, event); + } + AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE); switch (keyCode) { 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..1b4e8b81e 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java @@ -2,22 +2,19 @@ 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; @@ -29,7 +26,10 @@ public class WidgetConfigActivity extends AppCompatActivity { 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,6 +118,9 @@ 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(); 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 002147071..4fa8acc43 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/ChaptersListAdapter.java @@ -22,12 +22,14 @@ 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.core.util.playback.Playable; +import de.danoeh.antennapod.view.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); @@ -65,7 +66,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte holder.duration.setText(context.getString(R.string.chapter_duration, Converter.getDurationStringLocalized(context, (int) duration))); - if (sc.getLink() == null) { + if (TextUtils.isEmpty(sc.getLink())) { holder.link.setVisibility(View.GONE); } else { holder.link.setVisibility(View.VISIBLE); @@ -73,6 +74,7 @@ public class ChaptersListAdapter extends RecyclerView.Adapter<ChaptersListAdapte holder.link.setOnClickListener(v -> IntentUtils.openInBrowser(context, sc.getLink())); } holder.secondaryActionIcon.setImageResource(ThemeUtils.getDrawableFromAttr(context, R.attr.av_play)); + holder.secondaryActionButton.setContentDescription(context.getString(R.string.play_chapter)); holder.secondaryActionButton.setOnClickListener(v -> { if (callback != null) { callback.onPlayChapterButtonClicked(position); @@ -82,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) { @@ -135,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); @@ -145,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..0c4aaf6ed 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; 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..268a21409 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadlistAdapter.java @@ -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/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..7a5cf431f 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java +++ b/app/src/main/java/de/danoeh/antennapod/config/ClientConfigurator.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.config; import de.danoeh.antennapod.BuildConfig; import de.danoeh.antennapod.core.ClientConfig; +import de.danoeh.antennapod.core.storage.APDownloadAlgorithm; /** * Configures the ClientConfig class of the core package. @@ -15,7 +16,7 @@ class ClientConfigurator { ClientConfig.applicationCallbacks = new ApplicationCallbacksImpl(); ClientConfig.downloadServiceCallbacks = new DownloadServiceCallbacksImpl(); ClientConfig.playbackServiceCallbacks = new PlaybackServiceCallbacksImpl(); - ClientConfig.dbTasksCallbacks = new DBTasksCallbacksImpl(); + ClientConfig.automaticDownloadAlgorithm = new APDownloadAlgorithm(); 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..f509f6dd4 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java @@ -31,7 +31,6 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { public PendingIntent getAuthentificationNotificationContentIntent(Context context, DownloadRequest request) { final Intent activityIntent = new Intent(context.getApplicationContext(), DownloadAuthenticationActivity.class); 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 +53,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 index 44f18e9da..f70cb26ff 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/PlaybackServiceCallbacksImpl.java @@ -3,7 +3,6 @@ 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; @@ -22,10 +21,4 @@ public class PlaybackServiceCallbacksImpl implements PlaybackServiceCallbacks { return i; } } - - @Override - public boolean useQueue() { - return true; - } - } 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..efaff1da3 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/EpisodesApplyActionFragment.java @@ -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 82010637f..80df87891 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/FilterDialog.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.dialog; import android.content.Context; import android.text.TextUtils; import android.view.LayoutInflater; +import android.view.View; import android.widget.LinearLayout; import android.widget.RadioButton; @@ -34,18 +35,19 @@ public abstract class FilterDialog { builder.setTitle(R.string.filter); LayoutInflater inflater = LayoutInflater.from(this.context); - LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.filter_dialog, null, false); + View layout = inflater.inflate(R.layout.filter_dialog, null, false); + LinearLayout rows = layout.findViewById(R.id.filter_rows); builder.setView(layout); for (FeedItemFilterGroup item : FeedItemFilterGroup.values()) { - RecursiveRadioGroup row = (RecursiveRadioGroup) inflater.inflate(R.layout.filter_dialog_row, null); + RecursiveRadioGroup row = (RecursiveRadioGroup) inflater.inflate(R.layout.filter_dialog_row, null, false); RadioButton filter1 = row.findViewById(R.id.filter_dialog_radioButton1); RadioButton filter2 = row.findViewById(R.id.filter_dialog_radioButton2); filter1.setText(item.values[0].displayName); filter1.setTag(item.values[0].filterId); filter2.setText(item.values[1].displayName); filter2.setTag(item.values[1].filterId); - layout.addView(row); + rows.addView(row); } for (String filterId : filterValues) { @@ -56,11 +58,11 @@ public abstract class FilterDialog { builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { filterValues.clear(); - for (int i = 0; i < layout.getChildCount(); i++) { - if (!(layout.getChildAt(i) instanceof RecursiveRadioGroup)) { + for (int i = 0; i < rows.getChildCount(); i++) { + if (!(rows.getChildAt(i) instanceof RecursiveRadioGroup)) { continue; } - RecursiveRadioGroup group = (RecursiveRadioGroup) layout.getChildAt(i); + RecursiveRadioGroup group = (RecursiveRadioGroup) rows.getChildAt(i); if (group.getCheckedButton() != null) { String tag = (String) group.getCheckedButton().getTag(); if (tag != null) { // Clear buttons use no tag 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/RemoveFeedDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java new file mode 100644 index 000000000..156c1dba8 --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/dialog/RemoveFeedDialog.java @@ -0,0 +1,52 @@ +package de.danoeh.antennapod.dialog; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.util.Log; +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.dialog.ConfirmationDialog; +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.storage.DBWriter; +import io.reactivex.Completable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + +public class RemoveFeedDialog { + private static final String TAG = "RemoveFeedDialog"; + + public static void show(Context context, Feed feed, Runnable onSuccess) { + int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg + : R.string.feed_delete_confirmation_msg; + String message = context.getString(messageId, feed.getTitle()); + + ConfirmationDialog dialog = new ConfirmationDialog(context, R.string.remove_feed_label, message) { + @Override + public void onConfirmButtonPressed(DialogInterface clickedDialog) { + clickedDialog.dismiss(); + + ProgressDialog progressDialog = new ProgressDialog(context); + progressDialog.setMessage(context.getString(R.string.feed_remover_msg)); + progressDialog.setIndeterminate(true); + progressDialog.setCancelable(false); + progressDialog.show(); + + Completable.fromCallable(() -> DBWriter.deleteFeed(context, feed.getId()).get()) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + () -> { + Log.d(TAG, "Feed was deleted"); + if (onSuccess != null) { + onSuccess.run(); + } + progressDialog.dismiss(); + }, error -> { + Log.e(TAG, Log.getStackTraceString(error)); + progressDialog.dismiss(); + }); + } + }; + dialog.createNewDialog().show(); + } +} 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 a8915480c..8a87fef25 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/SubscriptionsFilterDialog.java @@ -30,7 +30,8 @@ public class SubscriptionsFilterDialog { builder.setTitle(context.getString(R.string.pref_filter_feed_title)); LayoutInflater inflater = LayoutInflater.from(context); - LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.filter_dialog, null, false); + View layout = inflater.inflate(R.layout.filter_dialog, null, false); + LinearLayout rows = layout.findViewById(R.id.filter_rows); builder.setView(layout); for (SubscriptionsFilterGroup item : SubscriptionsFilterGroup.values()) { @@ -45,7 +46,7 @@ public class SubscriptionsFilterDialog { } else { filter2.setVisibility(View.GONE); } - layout.addView(row); + rows.addView(row); } for (String filterId : filterValues) { @@ -56,11 +57,11 @@ public class SubscriptionsFilterDialog { builder.setPositiveButton(R.string.confirm_label, (dialog, which) -> { filterValues.clear(); - for (int i = 0; i < layout.getChildCount(); i++) { - if (!(layout.getChildAt(i) instanceof RecursiveRadioGroup)) { + for (int i = 0; i < rows.getChildCount(); i++) { + if (!(rows.getChildAt(i) instanceof RecursiveRadioGroup)) { continue; } - RecursiveRadioGroup group = (RecursiveRadioGroup) layout.getChildAt(i); + RecursiveRadioGroup group = (RecursiveRadioGroup) rows.getChildAt(i); if (group.getCheckedButton() != null) { String tag = (String) group.getCheckedButton().getTag(); if (tag != null) { // Clear buttons use no tag 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/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java index caf489569..06a974dfd 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,39 +60,43 @@ 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); intentGetContentAction.setType("*/*"); startActivityForResult(intentGetContentAction, REQUEST_CODE_CHOOSE_OPML_IMPORT_PATH); } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); + e.printStackTrace(); + ((MainActivity) getActivity()) + .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; } @@ -100,29 +105,35 @@ public class AddFeedFragment extends Fragment { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivityForResult(intent, REQUEST_CODE_ADD_LOCAL_FOLDER); } catch (ActivityNotFoundException e) { - Log.e(TAG, "No activity found. Should never happen..."); + e.printStackTrace(); + ((MainActivity) getActivity()) + .showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG); } }); 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(); } @@ -134,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; @@ -187,7 +197,6 @@ public class AddFeedFragment extends Fragment { throw new IllegalArgumentException("Unable to retrieve document tree"); } Feed dirFeed = new Feed(Feed.PREFIX_LOCAL_FOLDER + uri.toString(), null, documentFile.getName()); - dirFeed.setDescription(getString(R.string.local_feed_description)); dirFeed.setItems(Collections.emptyList()); dirFeed.setSortOrder(SortOrder.EPISODE_TITLE_A_Z); Feed fromDatabase = DBTasks.updateFeed(getContext(), dirFeed, false); 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 08da1f8d9..77b272da7 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; 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..82e2b3a6a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/AudioPlayerFragment.java @@ -22,6 +22,8 @@ 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; @@ -43,7 +45,6 @@ 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 io.reactivex.Maybe; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -74,7 +75,6 @@ public class AudioPlayerFragment extends Fragment implements PlaybackSpeedIndicatorView butPlaybackSpeed; TextView txtvPlaybackSpeed; private ViewPager2 pager; - private PagerIndicatorView pageIndicator; private TextView txtvPosition; private TextView txtvLength; private SeekBar sbPosition; @@ -90,6 +90,8 @@ public class AudioPlayerFragment extends Fragment implements private PlaybackController controller; private Disposable disposable; private boolean showTimeLeft; + private boolean hasChapters = false; + private TabLayoutMediator tabLayoutMediator; @Override public View onCreateView(@NonNull LayoutInflater inflater, @@ -141,13 +143,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); } @@ -362,10 +388,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); 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..d781d0774 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ChaptersFragment.java @@ -106,6 +106,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/DownloadLogFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/DownloadLogFragment.java index 98d6a946b..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; @@ -176,7 +174,7 @@ public class DownloadLogFragment extends ListFragment { public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (!super.onOptionsItemSelected(item)) { switch (item.getItemId()) { - case R.id.clear_history_item: + case R.id.clear_logs_item: DBWriter.clearDownloadLog(); return true; case R.id.refresh_item: 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 4ca08cbdd..ddfcd06ad 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java @@ -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,8 +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_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 681d3a88e..8e14214d2 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedItemlistFragment.java @@ -1,7 +1,6 @@ package de.danoeh.antennapod.fragment; import android.content.Context; -import android.content.DialogInterface; import android.content.res.Configuration; import android.content.Intent; import android.graphics.LightingColorFilter; @@ -36,8 +35,6 @@ import com.joanzapata.iconify.widget.IconTextView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.EpisodeItemListAdapter; -import de.danoeh.antennapod.core.asynctask.FeedRemover; -import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.DownloaderUpdate; @@ -65,6 +62,7 @@ import de.danoeh.antennapod.core.util.ThemeUtils; import de.danoeh.antennapod.core.util.gui.MoreContentListFooterUtil; import de.danoeh.antennapod.dialog.EpisodesApplyActionFragment; import de.danoeh.antennapod.dialog.FilterDialog; +import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.RenameFeedDialog; import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler; import de.danoeh.antennapod.menuhandler.FeedMenuHandler; @@ -298,28 +296,8 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem new RenameFeedDialog(getActivity(), feed).show(); return true; case R.id.remove_item: - final FeedRemover remover = new FeedRemover( - getActivity(), feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); - } - }; - int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg - : R.string.feed_delete_confirmation_msg; - ConfirmationDialog conDialog = new ConfirmationDialog(getActivity(), - R.string.remove_feed_label, - getString(messageId, feed.getTitle())) { - - @Override - public void onConfirmButtonPressed( - DialogInterface dialog) { - dialog.dismiss(); - remover.executeAsync(); - } - }; - conDialog.createNewDialog().show(); + RemoveFeedDialog.show(getContext(), feed, () -> + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null)); return true; default: return false; 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..07f59bb42 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; @@ -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() @@ -319,7 +319,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/NavDrawerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java index 4f99e8130..c69d46265 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/NavDrawerFragment.java @@ -26,19 +26,15 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.adapter.NavListAdapter; -import de.danoeh.antennapod.core.asynctask.FeedRemover; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.FeedItemUtil; -import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; import de.danoeh.antennapod.dialog.RenameFeedDialog; import io.reactivex.Observable; @@ -192,41 +188,15 @@ public class NavDrawerFragment extends Fragment implements AdapterView.OnItemCli new RenameFeedDialog(getActivity(), feed).show(); return true; case R.id.remove_item: - final FeedRemover remover = new FeedRemover(getContext(), feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - if (selectedNavListIndex == position) { - if (getActivity() instanceof MainActivity) { - ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); - } else { - showMainActivity(EpisodesFragment.TAG); - } - } - } - }; - int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg - : R.string.feed_delete_confirmation_msg; - ConfirmationDialog conDialog = new ConfirmationDialog(getContext(), - R.string.remove_feed_label, - getString(messageId, feed.getTitle())) { - @Override - public void onConfirmButtonPressed(DialogInterface dialog) { - dialog.dismiss(); - long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); - if (mediaId > 0 && FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { - Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); - remover.skipOnCompletion = true; - int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); - if (playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { - IntentUtils.sendLocalBroadcast(getContext(), - PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE); - } + RemoveFeedDialog.show(getContext(), feed, () -> { + if (selectedNavListIndex == position) { + if (getActivity() instanceof MainActivity) { + ((MainActivity) getActivity()).loadFragment(EpisodesFragment.TAG, null); + } else { + showMainActivity(EpisodesFragment.TAG); } - remover.executeAsync(); } - }; - conDialog.createNewDialog().show(); + }); return true; default: return super.onContextItemSelected(item); 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/OnlineSearchFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java index ba817650b..992b6930c 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/OnlineSearchFragment.java @@ -90,7 +90,6 @@ public class OnlineSearchFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_itunes_search, container, false); - setupToolbar(root.findViewById(R.id.toolbar)); root.findViewById(R.id.spinner_country).setVisibility(INVISIBLE); gridView = root.findViewById(R.id.gridView); adapter = new ItunesAdapter(getActivity(), new ArrayList<>()); @@ -110,6 +109,7 @@ public class OnlineSearchFragment extends Fragment { txtvEmpty = root.findViewById(android.R.id.empty); TextView txtvPoweredBy = root.findViewById(R.id.search_powered_by); txtvPoweredBy.setText(getString(R.string.search_powered_by, searchProvider.getName())); + setupToolbar(root.findViewById(R.id.toolbar)); return root; } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java index 4549e2a09..973fcb978 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java @@ -20,6 +20,7 @@ import de.danoeh.antennapod.core.event.FeedItemEvent; import de.danoeh.antennapod.core.event.PlaybackHistoryEvent; import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; @@ -180,6 +181,12 @@ public class PlaybackHistoryFragment extends Fragment implements Toolbar.OnMenuI refreshToolbarState(); } + @Subscribe(threadMode = ThreadMode.MAIN) + public void onUnreadItemsChanged(UnreadItemsUpdateEvent event) { + loadItems(); + refreshToolbarState(); + } + private void onFragmentLoaded() { adapter.notifyDataSetChanged(); refreshToolbarState(); 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 3b4246e3a..4735cab42 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/SubscriptionFragment.java @@ -33,22 +33,18 @@ import java.util.concurrent.Callable; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.SubscriptionsAdapter; -import de.danoeh.antennapod.core.asynctask.FeedRemover; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; import de.danoeh.antennapod.core.event.DownloadEvent; import de.danoeh.antennapod.core.event.FeedListUpdateEvent; import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.Feed; -import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; -import de.danoeh.antennapod.core.service.playback.PlaybackService; 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.core.util.FeedItemUtil; -import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.download.AutoUpdateManager; +import de.danoeh.antennapod.dialog.RemoveFeedDialog; import de.danoeh.antennapod.dialog.SubscriptionsFilterDialog; import de.danoeh.antennapod.dialog.FeedSortDialog; import de.danoeh.antennapod.dialog.RenameFeedDialog; @@ -299,45 +295,13 @@ public class SubscriptionFragment extends Fragment implements Toolbar.OnMenuItem new RenameFeedDialog(getActivity(), feed).show(); return true; case R.id.remove_item: - displayRemoveFeedDialog(feed); + RemoveFeedDialog.show(getContext(), feed, null); return true; default: return super.onContextItemSelected(item); } } - private void displayRemoveFeedDialog(Feed feed) { - final FeedRemover remover = new FeedRemover(getContext(), feed) { - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - loadSubscriptions(); - } - }; - - int messageId = feed.isLocalFeed() ? R.string.feed_delete_confirmation_local_msg - : R.string.feed_delete_confirmation_msg; - String message = getString(messageId, feed.getTitle()); - ConfirmationDialog dialog = new ConfirmationDialog(getContext(), R.string.remove_feed_label, message) { - @Override - public void onConfirmButtonPressed(DialogInterface clickedDialog) { - clickedDialog.dismiss(); - long mediaId = PlaybackPreferences.getCurrentlyPlayingFeedMediaId(); - if (mediaId > 0 && FeedItemUtil.indexOfItemWithMediaId(feed.getItems(), mediaId) >= 0) { - Log.d(TAG, "Currently playing episode is about to be deleted, skipping"); - remover.skipOnCompletion = true; - int playerStatus = PlaybackPreferences.getCurrentPlayerStatus(); - if(playerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) { - IntentUtils.sendLocalBroadcast(getContext(), PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE); - - } - } - remover.executeAsync(); - } - }; - dialog.createNewDialog().show(); - } - private <T> void displayConfirmationDialog(@StringRes int title, @StringRes int message, Callable<? extends T> task) { ConfirmationDialog dialog = new ConfirmationDialog(getActivity(), title, message) { @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java index 641e82b5a..ad815c3c9 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/GpodnetMainFragment.java @@ -1,11 +1,14 @@ package de.danoeh.antennapod.fragment.gpodnet; +import android.app.Activity; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; import androidx.viewpager2.adapter.FragmentStateAdapter; @@ -15,6 +18,9 @@ import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.activity.MainActivity; +import de.danoeh.antennapod.discovery.GpodnetPodcastSearcher; +import de.danoeh.antennapod.fragment.OnlineSearchFragment; /** * Main navigation hub for gpodder.net podcast directory @@ -30,9 +36,7 @@ public class GpodnetMainFragment extends Fragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View root = inflater.inflate(R.layout.pager_fragment, container, false); - Toolbar toolbar = root.findViewById(R.id.toolbar); - toolbar.setTitle(R.string.gpodnet_main_label); - toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); + setupToolbar(root.findViewById(R.id.toolbar)); ViewPager2 viewPager = root.findViewById(R.id.viewpager); GpodnetPagerAdapter pagerAdapter = new GpodnetPagerAdapter(this); @@ -59,6 +63,33 @@ public class GpodnetMainFragment extends Fragment { return root; } + private void setupToolbar(Toolbar toolbar) { + toolbar.setTitle(R.string.gpodnet_main_label); + toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); + + toolbar.inflateMenu(R.menu.search); + MenuItem searchItem = toolbar.getMenu().findItem(R.id.action_search); + final SearchView sv = (SearchView) searchItem.getActionView(); + sv.setQueryHint(getString(R.string.gpodnet_search_hint)); + sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + Activity activity = getActivity(); + if (activity != null) { + searchItem.collapseActionView(); + ((MainActivity) activity).loadChildFragment( + OnlineSearchFragment.newInstance(GpodnetPodcastSearcher.class, query)); + } + return true; + } + + @Override + public boolean onQueryTextChange(String s) { + return false; + } + }); + } + public static class GpodnetPagerAdapter extends FragmentStateAdapter { GpodnetPagerAdapter(@NonNull Fragment fragment) { @@ -68,20 +99,14 @@ public class GpodnetMainFragment extends Fragment { @NonNull @Override public Fragment createFragment(int position) { - Bundle arguments = new Bundle(); - arguments.putBoolean(PodcastListFragment.ARGUMENT_HIDE_TOOLBAR, true); switch (position) { case POS_TAGS: return new TagListFragment(); case POS_TOPLIST: - PodcastListFragment topListFragment = new PodcastTopListFragment(); - topListFragment.setArguments(arguments); - return topListFragment; + return new PodcastTopListFragment(); default: case POS_SUGGESTIONS: - PodcastListFragment suggestionsFragment = new SuggestionListFragment(); - suggestionsFragment.setArguments(arguments); - return suggestionsFragment; + return new SuggestionListFragment(); } } 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 b3f56941b..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; @@ -33,7 +30,6 @@ import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; * Displays a list of GPodnetPodcast-Objects in a GridView */ public abstract class PodcastListFragment extends Fragment { - public static final String ARGUMENT_HIDE_TOOLBAR = "hideToolbar"; private static final String TAG = "PodcastListFragment"; private GridView gridView; @@ -44,8 +40,6 @@ public abstract class PodcastListFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.gpodnet_podcast_list, container, false); - setupToolbar(root.findViewById(R.id.toolbar)); - gridView = root.findViewById(R.id.gridView); progressBar = root.findViewById(R.id.progressBar); txtvError = root.findViewById(R.id.txtvError); @@ -59,37 +53,6 @@ public abstract class PodcastListFragment extends Fragment { return root; } - private void setupToolbar(Toolbar toolbar) { - if (getArguments() != null && getArguments().getBoolean(ARGUMENT_HIDE_TOOLBAR, false)) { - toolbar.setVisibility(View.GONE); - return; - } - - toolbar.setTitle(R.string.gpodnet_main_label); - toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); - toolbar.inflateMenu(R.menu.gpodder_podcasts); - - MenuItem searchItem = toolbar.getMenu().findItem(R.id.action_search); - final SearchView sv = (SearchView) searchItem.getActionView(); - sv.setQueryHint(getString(R.string.gpodnet_search_hint)); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - sv.clearFocus(); - MainActivity activity = (MainActivity)getActivity(); - if (activity != null) { - activity.loadChildFragment(SearchListFragment.newInstance(s)); - } - return true; - } - - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - } - private void onPodcastSelected(GpodnetPodcast selection) { Log.d(TAG, "Selected podcast: " + selection.toString()); Intent intent = new Intent(getActivity(), OnlineFeedViewActivity.class); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java deleted file mode 100644 index 72a752bf1..000000000 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/SearchListFragment.java +++ /dev/null @@ -1,80 +0,0 @@ -package de.danoeh.antennapod.fragment.gpodnet; - -import android.os.Bundle; -import androidx.core.view.MenuItemCompat; -import androidx.appcompat.widget.SearchView; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; - -import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetService; -import de.danoeh.antennapod.core.sync.gpoddernet.GpodnetServiceException; -import de.danoeh.antennapod.core.sync.gpoddernet.model.GpodnetPodcast; -import org.apache.commons.lang3.Validate; - -import de.danoeh.antennapod.R; - -import java.util.List; - -/** - * Performs a search on the gpodder.net directory and displays the results. - */ -public class SearchListFragment extends PodcastListFragment { - private static final String ARG_QUERY = "query"; - - private String query; - - public static SearchListFragment newInstance(String query) { - SearchListFragment fragment = new SearchListFragment(); - Bundle args = new Bundle(); - args.putString(ARG_QUERY, query); - fragment.setArguments(args); - return fragment; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - if (getArguments() != null && getArguments().containsKey(ARG_QUERY)) { - this.query = getArguments().getString(ARG_QUERY); - } else { - this.query = ""; - } - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - // parent already inflated menu - MenuItem searchItem = menu.findItem(R.id.action_search); - final SearchView sv = (SearchView) searchItem.getActionView(); - sv.setQueryHint(getString(R.string.gpodnet_search_hint)); - sv.setQuery(query, false); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - sv.clearFocus(); - changeQuery(s); - return true; - } - - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - } - - @Override - protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException { - return service.searchPodcasts(query, 0); - } - - private void changeQuery(String query) { - Validate.notNull(query); - - this.query = query; - loadData(); - } -} diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java index 53a31b68d..2c41ee070 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/TagListFragment.java @@ -1,19 +1,11 @@ package de.danoeh.antennapod.fragment.gpodnet; -import android.app.Activity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; -import androidx.fragment.app.ListFragment; -import androidx.core.view.MenuItemCompat; -import androidx.appcompat.widget.SearchView; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.widget.TextView; - -import de.danoeh.antennapod.R; +import androidx.fragment.app.ListFragment; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.adapter.gpodnet.TagListAdapter; import de.danoeh.antennapod.core.preferences.GpodnetPreferences; @@ -28,37 +20,6 @@ public class TagListFragment extends ListFragment { private static final int COUNT = 50; @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - inflater.inflate(R.menu.gpodder_podcasts, menu); - MenuItem searchItem = menu.findItem(R.id.action_search); - final SearchView sv = (SearchView) searchItem.getActionView(); - sv.setQueryHint(getString(R.string.gpodnet_search_hint)); - sv.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String s) { - Activity activity = getActivity(); - if (activity != null) { - sv.clearFocus(); - ((MainActivity) activity).loadChildFragment(SearchListFragment.newInstance(s)); - } - return true; - } - - @Override - public boolean onQueryTextChange(String s) { - return false; - } - }); - } - - @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); 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..77f8063f2 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 { @@ -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/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/FeedItemMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java index 3d8f3fe09..1eecccb4c 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedItemMenuHandler.java @@ -48,65 +48,43 @@ public class FeedItemMenuHandler { if (menu == null || selectedItem == null) { return false; } - boolean hasMedia = selectedItem.getMedia() != null; - boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING; - - if (!isPlaying) { - setItemVisibility(menu, R.id.skip_episode_item, false); - } - boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE); - if (!isInQueue) { - setItemVisibility(menu, R.id.remove_from_queue_item, false); - } - if (!(!isInQueue && selectedItem.getMedia() != null)) { - setItemVisibility(menu, R.id.add_to_queue_item, false); - } - if (!ShareUtils.hasLinkToShare(selectedItem)) { - setItemVisibility(menu, R.id.visit_website_item, false); - } - - boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists(); + final boolean hasMedia = selectedItem.getMedia() != null; + final boolean isPlaying = hasMedia && selectedItem.getState() == FeedItem.State.PLAYING; + final boolean isInQueue = selectedItem.isTagged(FeedItem.TAG_QUEUE); + final boolean fileDownloaded = hasMedia && selectedItem.getMedia().fileExists(); + final boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE); + setItemVisibility(menu, R.id.skip_episode_item, isPlaying); + setItemVisibility(menu, R.id.remove_from_queue_item, isInQueue); + setItemVisibility(menu, R.id.add_to_queue_item, !isInQueue && selectedItem.getMedia() != null); + setItemVisibility(menu, R.id.visit_website_item, !selectedItem.getFeed().isLocalFeed() + && ShareUtils.hasLinkToShare(selectedItem)); + setItemVisibility(menu, R.id.share_item, !selectedItem.getFeed().isLocalFeed()); setItemVisibility(menu, R.id.remove_new_flag_item, selectedItem.isNew()); - if (selectedItem.isPlayed()) { - setItemVisibility(menu, R.id.mark_read_item, false); - } else { - setItemVisibility(menu, R.id.mark_unread_item, false); - } - - if (selectedItem.getMedia() == null || selectedItem.getMedia().getPosition() == 0) { - setItemVisibility(menu, R.id.reset_position, false); - } + setItemVisibility(menu, R.id.mark_read_item, !selectedItem.isPlayed()); + setItemVisibility(menu, R.id.mark_unread_item, selectedItem.isPlayed()); + setItemVisibility(menu, R.id.reset_position, hasMedia && selectedItem.getMedia().getPosition() != 0); if (!UserPreferences.isEnableAutodownload() || fileDownloaded || selectedItem.getFeed().isLocalFeed()) { setItemVisibility(menu, R.id.activate_auto_download, false); setItemVisibility(menu, R.id.deactivate_auto_download, false); - } else if (selectedItem.getAutoDownload()) { - setItemVisibility(menu, R.id.activate_auto_download, false); } else { - setItemVisibility(menu, R.id.deactivate_auto_download, false); + setItemVisibility(menu, R.id.activate_auto_download, !selectedItem.getAutoDownload()); + setItemVisibility(menu, R.id.deactivate_auto_download, selectedItem.getAutoDownload()); } // Display proper strings when item has no media - if (!hasMedia && !selectedItem.isPlayed()) { + if (hasMedia) { + setItemTitle(menu, R.id.mark_read_item, R.string.mark_read_label); + setItemTitle(menu, R.id.mark_unread_item, R.string.mark_unread_label); + } else { setItemTitle(menu, R.id.mark_read_item, R.string.mark_read_no_media_label); - } - - if (!hasMedia && selectedItem.isPlayed()) { setItemTitle(menu, R.id.mark_unread_item, R.string.mark_unread_label_no_media); } - boolean isFavorite = selectedItem.isTagged(FeedItem.TAG_FAVORITE); setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite); setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite); - setItemVisibility(menu, R.id.remove_item, fileDownloaded); - - if (selectedItem.getFeed().isLocalFeed()) { - setItemVisibility(menu, R.id.visit_website_item, false); - setItemVisibility(menu, R.id.share_item, false); - } - return true; } @@ -134,7 +112,7 @@ public class FeedItemMenuHandler { * @param id The id of the string that is going to be replaced. * @param noMedia The id of the new String that is going to be used. * */ - public static void setItemTitle(Menu menu, int id, int noMedia){ + public static void setItemTitle(Menu menu, int id, int noMedia) { MenuItem item = menu.findItem(id); if (item != null) { item.setTitle(noMedia); 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/view/CircularProgressBar.java b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java index f755a4c84..2fd570ece 100644 --- a/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java +++ b/app/src/main/java/de/danoeh/antennapod/view/CircularProgressBar.java @@ -11,7 +11,8 @@ import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.util.ThemeUtils; public class CircularProgressBar extends View { - private static final float EPSILON = 0.005f; + public static final float MINIMUM_PERCENTAGE = 0.005f; + public static final float MAXIMUM_PERCENTAGE = 1 - MINIMUM_PERCENTAGE; private final Paint paintBackground = new Paint(); private final Paint paintProgress = new Paint(); @@ -74,11 +75,11 @@ public class CircularProgressBar extends View { bounds.set(padding, padding, getWidth() - padding, getHeight() - padding); canvas.drawArc(bounds, 0, 360, false, paintBackground); - if (percentage > EPSILON && 1 - percentage > EPSILON) { + if (MINIMUM_PERCENTAGE <= percentage && percentage <= MAXIMUM_PERCENTAGE) { canvas.drawArc(bounds, -90, percentage * 360, false, paintProgress); } - if (Math.abs(percentage - targetPercentage) > EPSILON) { + if (Math.abs(percentage - targetPercentage) > MINIMUM_PERCENTAGE) { float speed = 0.02f; if (Math.abs(targetPercentage - percentage) < 0.1 && targetPercentage > percentage) { speed = 0.006f; 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/play/listings/ca/full-description.txt b/app/src/main/play/listings/ca/full-description.txt new file mode 100644 index 000000000..90227a4c0 --- /dev/null +++ b/app/src/main/play/listings/ca/full-description.txt @@ -0,0 +1,31 @@ +AntennaPod és un reproductor i organitzador de podcasts que et dona accés instantani a milions de podcasts tant de pagament com gratuïts, des de podcasters independents fins a grans publicacions com la BBC, NPR i CNN. Afegeix, importa i exporta els seus feeds sense problemes usant la base de dates de podcasts de iTunes, arxius OPML o simples URLs RSS. +Descarrega, transmet o posa en cola episodis i disfruta'ls com tu vulgues amb velocitats de reproducció ajustables, suport per a capítols i un temporitzador per a dormir. +Estalvia esforç, bateria i us de dades mòbils amb controls d'automatització per a descarregar (hores específiques, intervals i xarxes Wi-Fi) i esborrar episodis (basant-se en els teus favorits i els ajustos de retard). + +Fet per entusiastes dels podcasts, AntennaPod es lliure en tots els sentits de la paraula: codi obert, sense cost, sense anuncis. + +<b>Importa, organitza i reprodueix</b> +• Organitza la reproducció desde qualsevol lloc: widget a la pantalla principal, notificacions de sistema i controls d'auricular amb fil o bluetooth. +• Afegeix i importa feeds via iTunes, gPodder.net, fitxers OPML o enllaços RSS o Atom. +• Disfruta escoltant a la teua manera amb velocitat de reproducció ajustable, suport per a capitols, memòria de posició de reproducció i un avançat temporitzador per a dormir (que pots resetetjar agitant el mòbil) +• Accedix a feeds i episodis protegits amb contrasenya + +<b>Organitza, comparteix i aprecia</b> +• Recorda als millors dels millors marcant episodis com a favorits. +• Troba un episodi usant el historial de reproducció o buscant títols i notes. +• Comparteix episodis i feeds a través d'opcions avançades de medis socials i email, els serveis de gPodder.net i via l'exportació OPML. + +<b>Controla el sistema</b> +• Pren control de les descarregues automàtiques: tria feeds, exclou xarxes mòbils, selecciona xarxes WiFi específiques, requereix que el telèfon estiga carregant i selecciona hores o intervals. +• Gestioneu l'emmagatzematge ajustant la quantitat d'episodis en emmagatzematge temporal, l'esborrat intel·ligent i triant la vostra ubicació preferida. +• Adapteu-vos al vostre entorn fent servir el tema clar o el fosc. +• Feu còpies de seguretat de les vostres subscripcions amb la integració amb gPodder.net i l'exportació a OPML. + +<b>Uniu-vos a la comunitat d'AntennaPod!</b> +L'AntennaPod el desenvolupen voluntaris. Podeu col·laborar, amb codi o comentaris. + +Els amigables membres del nostre fòrum estaran contents d'ajudar-vos amb qualsevol pregunta. Esteu convidats a parlar de les features i de podcasting en general, a més. +https://forum.antennapod.org/ + +Transifex és el lloc on podeu ajudar amb les traduccions. +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/ca/short-description.txt b/app/src/main/play/listings/ca/short-description.txt new file mode 100644 index 000000000..25b884d1b --- /dev/null +++ b/app/src/main/play/listings/ca/short-description.txt @@ -0,0 +1 @@ +Reproductor i organitzador de podcasts, fàcil d'usar, flexible i de codi obert.
\ No newline at end of file diff --git a/app/src/main/play/listings/ca/title.txt b/app/src/main/play/listings/ca/title.txt new file mode 100644 index 000000000..31552f353 --- /dev/null +++ b/app/src/main/play/listings/ca/title.txt @@ -0,0 +1 @@ +AntennaPod
\ No newline at end of file diff --git a/app/src/main/play/listings/cs-CZ/full-description.txt b/app/src/main/play/listings/cs-CZ/full-description.txt new file mode 100644 index 000000000..8411a2e7f --- /dev/null +++ b/app/src/main/play/listings/cs-CZ/full-description.txt @@ -0,0 +1,31 @@ +AntennaPod je správce a přehrávač podcastů, co vám umožňuje okamžitý přístup k milionům podcastů (placené i zdarma) od nezávislých autorů, přes velké zahraniční korporace jako BBC, NPR a CNN, až po české DVTV nebo Český rozhlas. Své podcasty můžete jednoduše přidávat, importovat i exportovat pomocí iTunes, OPML souborů nebo RSS. +Stahujte, streamujte nebo si vytvořte frontu epizod a užijte si poslech tak, jak ho máte rádi s nastavitelnou rychlostí přehrávání, podporou kapitol a s časovačem vypnutí. +Ušetři si námahu, baterku i mobilní data s pomocí robustní automatické kontroly nad stahováním epizod (urči časy, intervaly a WIFI sítě) a mazáním epizod (na základě oblíbenosti a nastavení zpoždění). + +Vytvořeno nadšenci do podcastů, AntennaPod je otevřený software (OSS), zdarma a bez reklam. + +<b>Importujte, zorganizujte a přehrávejte</b> +• Ovládejte přehrávání odkudkoli: z widgetu na domovské obrazovce, z oznámení nebo pomocí tlačítek na sluchátkách včetně Bluetooth +• Přidejte a importujte podcasty přes iTunes anebo gPodder.net, OPML soubory a RSS anebo Atom odkazy +• Užijte si poslech s nastavitelnou rychlostí přehrávání, podporou kapitol, zapamatování poslední pozice přehrávání a pokročilým časovačem vypnutí (restart zatřesením, snížení hlasitosti) +• Přistupujte k zaheslovaným podcastům a epizodám + +<b>Udržovat přehled, sdílet & ocenit</b> +• Udržujte si seznam toho nejlepšího z nejlepšího přidáním epizod do oblíbených +• Vyhledej tu správnou epizodu ve své historii přehrávání nebo prohledáním jmen a popisů epizod +• Sdílej epizody a kanály pomocí pokročilých nastavení pro sociální média a email, gPodder.net službu nebo OPML export + +<b>Ovládat systém</b> +• Převezmi kontrolu nad automatickým stahováním: vybírej kanály, vyluč mobilní sítě, vyber specifické WIFI sítě, vyžaduj stav nabíjení telefonu a nastav časy nebo intervaly +• Spravujte využití úložiště nastavením množství uložených epizod, chytrého mazání a výběrem místa uložení +• Přizpůsobte si aplikaci svému prostředí pomocí světlého nebo tmavého motivu +• Zálohujte své sbírky pomocí služby gPodder.net nebo exportem OPML souborů + +<b>Přidejte se do komunity AntennaPodu!</b> +AntennaPod je aktivně vyvíjen dobrovolníky. Můžete přispět také svým kódem nebo komentáři! + +Naše přátelská členská základna vám ráda zodpoví jakékoli dotazy. Zveme vás k diskuzi o AntennaPodu nebo i podcastech obecně. +https://forum.antennapod.org/ + +Na Transifexu můžete pomoct s překladem: +https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/listings/cs-CZ/short-description.txt b/app/src/main/play/listings/cs-CZ/short-description.txt new file mode 100644 index 000000000..e0df116ee --- /dev/null +++ b/app/src/main/play/listings/cs-CZ/short-description.txt @@ -0,0 +1 @@ +Jednoduchý a flexibilní open-source program pro správu a poslech podcastů
\ No newline at end of file diff --git a/app/src/main/play/listings/cs-CZ/title.txt b/app/src/main/play/listings/cs-CZ/title.txt new file mode 100644 index 000000000..31552f353 --- /dev/null +++ b/app/src/main/play/listings/cs-CZ/title.txt @@ -0,0 +1 @@ +AntennaPod
\ No newline at end of file diff --git a/app/src/main/play/listings/de-DE/graphics/large-tablet-screenshots/tablet.png b/app/src/main/play/listings/de-DE/graphics/large-tablet-screenshots/tablet.png Binary files differnew file mode 100644 index 000000000..5ff81f1e0 --- /dev/null +++ b/app/src/main/play/listings/de-DE/graphics/large-tablet-screenshots/tablet.png diff --git a/app/src/main/play/listings/en-US/graphics/large-tablet-screenshots/tablet.png b/app/src/main/play/listings/en-US/graphics/large-tablet-screenshots/tablet.png Binary files differnew file mode 100644 index 000000000..2defb5ce9 --- /dev/null +++ b/app/src/main/play/listings/en-US/graphics/large-tablet-screenshots/tablet.png diff --git a/app/src/main/play/listings/es-ES/full-description.txt b/app/src/main/play/listings/es-ES/full-description.txt index b15dd1762..52897ce9c 100644 --- a/app/src/main/play/listings/es-ES/full-description.txt +++ b/app/src/main/play/listings/es-ES/full-description.txt @@ -24,8 +24,8 @@ Creado por entusiastas del pódcast, AntennaPod es libre en todos los sentidos: <b>¡Únete a la comunidad AntennaPod!</b> AntennaPod es desarrollado por voluntarios. ¡Tú también puedes contribuir, con tu código o con tus comentarios! -Visita GitHub para solicitar características nuevas, reportar fallos y contribuir con código: -https://www.github.com/AntennaPod/AntennaPod +Nuestros amables miembros del foro te ayudarán con cualquier duda que tengas. Estás invitado a discutir sobre las características y el podcasting en general. +https://forum.antennapod.org/ Ayuda con las traducciones en Transifex: https://www.transifex.com/antennapod/antennapod
\ No newline at end of file diff --git a/app/src/main/play/release-notes/en-US/default.txt b/app/src/main/play/release-notes/en-US/default.txt index a27f2d0c0..c911a4f1f 100644 --- a/app/src/main/play/release-notes/en-US/default.txt +++ b/app/src/main/play/release-notes/en-US/default.txt @@ -1,7 +1,7 @@ -We are proud to release version 2.0 with a new logo and refreshed user interface. -Thank you to 6420 users who participated in the vote for the new logo! - -- Support for chapter images (only new episodes, by @ByteHamster) -- Skip intro and ending per feed (by @tonytamsf) -- Option to show notifications after episodes have been auto-downloaded (by @shortspider) -- Bug fixes and improvements (by @ebraminio, @tonytamsf, @JessieVela, @ByteHamster and more) +- A long-standing wish of many: playing local files! In the 'Add podcast' screen simply tap 'Add local folder' and select a location on your phone! (@ByteHamster, @igoralmeida & @damoasda) +- Pick a country for the 'Discover' screen (@tonytamsf) +- Keyboard shortcuts (@asdoi) +- Search the PodcastIndex.org database (@edwinhere) +- Pull to refresh (@asdoi) +- Playback speed & filter dialogs (@ByteHamster & @bws9000) +- Smooth sleep timer volume (@olivoto) 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..3b065cefc 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"/> 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/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.xml b/app/src/main/res/layout/filter_dialog.xml index 39e9258d9..d700f0365 100644 --- a/app/src/main/res/layout/filter_dialog.xml +++ b/app/src/main/res/layout/filter_dialog.xml @@ -1,11 +1,15 @@ <?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:paddingLeft="24dp" - android:paddingTop="24dp" - android:paddingRight="24dp" - android:paddingBottom="8dp"> - -</LinearLayout>
\ No newline at end of file +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <LinearLayout + android:id="@+id/filter_rows" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingLeft="24dp" + android:paddingTop="24dp" + android:paddingRight="24dp" + android:paddingBottom="8dp"> + </LinearLayout> +</ScrollView> diff --git a/app/src/main/res/layout/filter_dialog_row.xml b/app/src/main/res/layout/filter_dialog_row.xml index 0863997b3..5011812d9 100644 --- a/app/src/main/res/layout/filter_dialog_row.xml +++ b/app/src/main/res/layout/filter_dialog_row.xml @@ -28,7 +28,7 @@ android:layout_marginRight="2dp" android:layout_weight="1" android:background="?attr/filter_dialog_button_background" - android:button="@android:color/transparent" + style="@style/NoButtonRadio" android:foreground="?android:attr/selectableItemBackground" android:checked="false" android:gravity="center" @@ -40,7 +40,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:background="?attr/filter_dialog_button_background" - android:button="@android:color/transparent" + style="@style/NoButtonRadio" android:foreground="?android:attr/selectableItemBackground" android:checked="false" android:gravity="center" @@ -53,7 +53,7 @@ android:layout_width="48dp" android:layout_height="48dp" android:background="@drawable/ic_filter_close" - android:button="@android:color/transparent" + style="@style/NoButtonRadio" android:foreground="?android:attr/selectableItemBackground" android:layout_gravity="center_vertical" android:checked="true" /> diff --git a/app/src/main/res/layout/gpodnet_podcast_list.xml b/app/src/main/res/layout/gpodnet_podcast_list.xml index ae0a400f1..a6918f8d4 100644 --- a/app/src/main/res/layout/gpodnet_podcast_list.xml +++ b/app/src/main/res/layout/gpodnet_podcast_list.xml @@ -2,18 +2,9 @@ <RelativeLayout 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="match_parent" android:layout_height="match_parent"> - <androidx.appcompat.widget.Toolbar - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="?attr/actionBarSize" - android:theme="?attr/actionBarTheme" - app:navigationIcon="?homeAsUpIndicator" - android:id="@+id/toolbar"/> - <GridView android:id="@+id/gridView" android:layout_width="match_parent" @@ -27,7 +18,7 @@ android:paddingTop="@dimen/list_vertical_padding" android:stretchMode="columnWidth" android:verticalSpacing="8dp" - android:layout_below="@id/toolbar" + android:layout_alignParentTop="true" tools:listitem="@layout/gpodnet_podcast_listitem" /> <ProgressBar 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/quick_feed_discovery.xml b/app/src/main/res/layout/quick_feed_discovery.xml index 0c55311e3..dd720afed 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" @@ -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/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/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/gpodder_podcasts.xml b/app/src/main/res/menu/gpodder_podcasts.xml deleted file mode 100644 index 93d93157a..000000000 --- a/app/src/main/res/menu/gpodder_podcasts.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<menu xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:custom="http://schemas.android.com/apk/res-auto"> - - <item - android:id="@+id/action_search" - android:icon="?attr/action_search" - custom:showAsAction="collapseActionView|ifRoom" - custom:actionViewClass="androidx.appcompat.widget.SearchView" - android:title="@string/search_label"/> - -</menu> 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 161332d2f..d2999c59d 100644 --- a/app/src/main/res/xml/preferences_playback.xml +++ b/app/src/main/res/xml/preferences_playback.xml @@ -114,7 +114,7 @@ android:title="@string/pref_skip_keeps_episodes_title"/> </PreferenceCategory> - <PreferenceCategory android:title="@string/media_player"> + <PreferenceCategory android:title="@string/experimental_pref"> <ListPreference android:defaultValue="exoplayer" android:entries="@array/media_player_options" @@ -122,9 +122,6 @@ android:title="@string/media_player" android:summary="@string/pref_media_player_message" android:entryValues="@array/media_player_values"/> - </PreferenceCategory> - - <PreferenceCategory android:title="@string/experimental_pref"> <SwitchPreferenceCompat android:defaultValue="false" android:enabled="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..a3cb53307 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 @@ -44,7 +45,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; |