summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java37
-rw-r--r--app/src/androidTest/java/de/test/antennapod/feed/FeedFilterTest.java130
-rw-r--r--app/src/androidTest/java/de/test/antennapod/feed/FeedItemTest.java43
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBWriterTest.java857
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/MainActivityTest.java45
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java102
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/URLCheckerTest.java157
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/playback/TimelineTest.java248
-rw-r--r--app/src/androidTest/java/de/test/antennapod/util/syndication/FeedDiscovererTest.java124
-rw-r--r--app/src/main/AndroidManifest.xml22
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java56
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java18
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java6
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/WidgetConfigActivity.java48
-rw-r--r--app/src/main/java/de/danoeh/antennapod/activity/gpoddernet/GpodnetAuthenticationActivity.java395
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/NavListAdapter.java2
-rw-r--r--app/src/main/java/de/danoeh/antennapod/dialog/GpodnetSetHostnameDialog.java59
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java10
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderAuthenticationFragment.java302
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/preferences/GpodderPreferencesFragment.java14
-rw-r--r--app/src/main/res/layout/activity_widget_config.xml29
-rw-r--r--app/src/main/res/layout/gpodnetauth_activity.xml10
-rw-r--r--app/src/main/res/layout/gpodnetauth_credentials.xml169
-rw-r--r--app/src/main/res/layout/gpodnetauth_device.xml125
-rw-r--r--app/src/main/res/layout/gpodnetauth_device_row.xml13
-rw-r--r--app/src/main/res/layout/gpodnetauth_dialog.xml20
-rw-r--r--app/src/main/res/layout/gpodnetauth_finish.xml32
-rw-r--r--app/src/main/res/layout/gpodnetauth_host.xml50
-rw-r--r--app/src/main/res/menu/feedinfo.xml26
-rw-r--r--app/src/main/res/xml/player_widget_info.xml3
-rw-r--r--app/src/main/res/xml/preferences_gpodder.xml14
31 files changed, 698 insertions, 2468 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java
index 3c8c5d7f0..21498effd 100644
--- a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java
+++ b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java
@@ -3,8 +3,10 @@ package de.test.antennapod;
import android.content.Context;
import android.content.Intent;
import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.preference.PreferenceManager;
+import androidx.test.espresso.NoMatchingViewException;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.espresso.PerformException;
import androidx.test.espresso.UiController;
@@ -15,6 +17,9 @@ import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.espresso.util.HumanReadables;
import androidx.test.espresso.util.TreeIterables;
import android.view.View;
+
+import junit.framework.AssertionFailedError;
+
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@@ -33,6 +38,7 @@ import java.util.concurrent.TimeoutException;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
@@ -57,7 +63,7 @@ public class EspressoTestUtils {
@Override
public String getDescription() {
- return "wait for a specific view for" + millis + " millis.";
+ return "wait for a specific view for " + millis + " millis.";
}
@Override
@@ -88,6 +94,33 @@ public class EspressoTestUtils {
}
/**
+ * Wait until a certain view becomes visible, but at the longest until the timeout.
+ * Unlike {@link #waitForView(Matcher, long)} it doesn't stick to the initial root view.
+ *
+ * @param viewMatcher The view to wait for.
+ * @param timeoutMillis Maximum waiting period in milliseconds.
+ * @throws Exception Throws an Exception in case of a timeout.
+ */
+ public static void waitForViewGlobally(@NonNull Matcher<View> viewMatcher, long timeoutMillis) throws Exception {
+ long startTime = System.currentTimeMillis();
+ long endTime = startTime + timeoutMillis;
+
+ do {
+ try {
+ onView(viewMatcher).check(matches(isDisplayed()));
+ // no Exception thrown -> check successful
+ return;
+ } catch (NoMatchingViewException | AssertionFailedError ignore) {
+ // check was not successful "not found" -> continue waiting
+ }
+ //noinspection BusyWait
+ Thread.sleep(50);
+ } while (System.currentTimeMillis() < endTime);
+
+ throw new Exception("Timeout after " + timeoutMillis + " ms");
+ }
+
+ /**
* Perform action of waiting for a specific view id.
* https://stackoverflow.com/a/30338665/
* @param id The id of the child to click.
@@ -113,7 +146,7 @@ public class EspressoTestUtils {
}
/**
- * Clear all app databases
+ * Clear all app databases.
*/
public static void clearPreferences() {
File root = InstrumentationRegistry.getInstrumentation().getTargetContext().getFilesDir().getParentFile();
diff --git a/app/src/androidTest/java/de/test/antennapod/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/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 3f7ebb48b..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 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,6 +74,7 @@ public class MainActivityTest {
@Test
public void testAddFeed() throws Exception {
+ // connect to podcast feed
uiTestUtils.addHostedFeedData();
final Feed feed = uiTestUtils.hostedFeeds.get(0);
openNavDrawer();
@@ -78,9 +82,14 @@ public class MainActivityTest {
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/util/FilenameGeneratorTest.java b/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java
deleted file mode 100644
index f376c75a5..000000000
--- a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package de.test.antennapod.util;
-
-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.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/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java
index deb2fe0db..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,17 +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.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;
@@ -50,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;
@@ -73,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;
@@ -663,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 18620a56a..f53c629b9 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -4,6 +4,7 @@ import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.LightingColorFilter;
import android.os.Build;
import android.os.Bundle;
@@ -87,6 +88,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
// Optional argument: specify a title for the actionbar.
private static final int RESULT_ERROR = 2;
private static final String TAG = "OnlineFeedViewActivity";
+ private static final String PREFS = "OnlineFeedViewActivityPreferences";
+ private static final String PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload";
+
private volatile List<Feed> feeds;
private Feed feed;
private String selectedDownloadUrl;
@@ -445,6 +449,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
IntentUtils.sendLocalBroadcast(this, PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE);
});
+ if (UserPreferences.isEnableAutodownload()) {
+ SharedPreferences preferences = getSharedPreferences(PREFS, MODE_PRIVATE);
+ viewBinding.autoDownloadCheckBox.setChecked(preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true));
+ }
+
final int MAX_LINES_COLLAPSED = 10;
description.setMaxLines(MAX_LINES_COLLAPSED);
description.setOnClickListener(v -> {
@@ -511,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/VideoplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java
index 1c8619e99..75198c016 100644
--- a/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java
+++ b/app/src/main/java/de/danoeh/antennapod/activity/VideoplayerActivity.java
@@ -17,7 +17,6 @@ import android.widget.ImageView;
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;
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 4805dba10..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,21 +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 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;
@@ -28,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) {
@@ -73,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() {
@@ -91,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/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/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/fragment/FeedInfoFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedInfoFragment.java
index abb597e60..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,9 +286,13 @@ public class FeedInfoFragment extends Fragment implements Toolbar.OnMenuItemClic
}
private void refreshToolbarState() {
+ boolean shareLinkVisible = feed != null && feed.getLink() != null;
+ boolean downloadUrlVisible = feed != null && !feed.isLocalFeed();
+
toolbar.getMenu().findItem(R.id.reconnect_local_folder).setVisible(feed != null && feed.isLocalFeed());
- toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(feed != null && !feed.isLocalFeed());
- toolbar.getMenu().findItem(R.id.share_link_item).setVisible(feed != null && feed.getLink() != null);
+ toolbar.getMenu().findItem(R.id.share_download_url_item).setVisible(downloadUrlVisible);
+ toolbar.getMenu().findItem(R.id.share_link_item).setVisible(shareLinkVisible);
+ toolbar.getMenu().findItem(R.id.share_parent).setVisible(downloadUrlVisible || shareLinkVisible);
toolbar.getMenu().findItem(R.id.visit_website_item).setVisible(feed != null && feed.getLink() != null
&& IntentUtils.isCallable(getContext(), new Intent(Intent.ACTION_VIEW, Uri.parse(feed.getLink()))));
}
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/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 eb23a5eb1..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
@@ -14,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) {
@@ -51,6 +48,7 @@ public class GpodderPreferencesFragment extends PreferenceFragmentCompat {
@Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
public void syncStatusChanged(SyncServiceEvent event) {
+ updateGpodnetPreferenceScreen();
if (!GpodnetPreferences.loggedIn()) {
return;
}
@@ -66,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,
@@ -94,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() {
@@ -119,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/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/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/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/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_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>