mirror of
https://github.com/AntennaPod/AntennaPod.git
synced 2025-10-29 11:49:33 +00:00
Compare commits
6 Commits
9db9dc7732
...
88e57cfb47
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88e57cfb47 | ||
|
|
428d1901b4 | ||
|
|
bc16763c5e | ||
|
|
720aba1602 | ||
|
|
60f4c4cb20 | ||
|
|
0772b4998d |
1
.github/workflows/checks.yml
vendored
1
.github/workflows/checks.yml
vendored
@ -49,6 +49,7 @@ jobs:
|
||||
run: |
|
||||
git diff --name-only | xargs -I '{}' echo "::error file={},line=1,endLine=1,title=XML Format::Run android-xml-formatter.jar on this file or view CI output to see how it should be formatted."
|
||||
python .github/workflows/errorPrinter.py
|
||||
false
|
||||
|
||||
unit-test:
|
||||
name: "Unit Test: ${{ matrix.variant }}"
|
||||
|
||||
@ -213,6 +213,10 @@ public class TranscriptDialogFragment extends DialogFragment
|
||||
doInitialScroll = false;
|
||||
|
||||
boolean quickScroll = Math.abs(layoutManager.findFirstVisibleItemPosition() - pos) > 5;
|
||||
if (layoutManager.findFirstVisibleItemPosition() < pos - 1
|
||||
&& !viewBinding.transcriptList.canScrollVertically(1)) {
|
||||
return;
|
||||
}
|
||||
if (quickScroll) {
|
||||
viewBinding.transcriptList.scrollToPosition(pos - 1);
|
||||
// Additionally, smooth scroll, so that currently active segment is on top of screen
|
||||
|
||||
@ -6,28 +6,22 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.event.PlayerStatusEvent;
|
||||
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
|
||||
import de.danoeh.antennapod.storage.preferences.UsageStatistics;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.ui.preferences.screen.AnimatedPreferenceFragment;
|
||||
import de.danoeh.antennapod.ui.screen.subscriptions.FeedSortDialog;
|
||||
import de.danoeh.antennapod.ui.screen.drawer.DrawerPreferencesDialog;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.ui.screen.drawer.DrawerPreferencesDialog;
|
||||
import de.danoeh.antennapod.ui.screen.subscriptions.SubscriptionsFilterDialog;
|
||||
import de.danoeh.antennapod.event.PlayerStatusEvent;
|
||||
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
|
||||
public class UserInterfacePreferencesFragment extends AnimatedPreferenceFragment {
|
||||
private static final String PREF_SWIPE = "prefSwipe";
|
||||
|
||||
@ -76,17 +70,6 @@ public class UserInterfacePreferencesFragment extends AnimatedPreferenceFragment
|
||||
showFullNotificationButtonsDialog();
|
||||
return true;
|
||||
});
|
||||
findPreference(UserPreferences.PREF_FILTER_FEED)
|
||||
.setOnPreferenceClickListener((preference -> {
|
||||
new SubscriptionsFilterDialog().show(getChildFragmentManager(), "filter");
|
||||
return true;
|
||||
}));
|
||||
|
||||
findPreference(UserPreferences.PREF_DRAWER_FEED_ORDER)
|
||||
.setOnPreferenceClickListener((preference -> {
|
||||
FeedSortDialog.showDialog(requireContext());
|
||||
return true;
|
||||
}));
|
||||
findPreference(PREF_SWIPE)
|
||||
.setOnPreferenceClickListener(preference -> {
|
||||
((PreferenceActivity) getActivity()).openScreen(R.xml.preferences_swipe);
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package de.danoeh.antennapod.ui.screen.subscriptions;
|
||||
|
||||
import android.content.Context;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.event.UnreadItemsUpdateEvent;
|
||||
import de.danoeh.antennapod.model.feed.FeedCounter;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class FeedCounterDialog {
|
||||
public static void showDialog(Context context) {
|
||||
MaterialAlertDialogBuilder dialog = new MaterialAlertDialogBuilder(context);
|
||||
dialog.setTitle(context.getString(R.string.pref_nav_drawer_feed_counter_title));
|
||||
dialog.setNegativeButton(android.R.string.cancel, (d, listener) -> d.dismiss());
|
||||
|
||||
int selected = UserPreferences.getFeedCounterSetting().id;
|
||||
List<String> entryValues =
|
||||
Arrays.asList(context.getResources().getStringArray(R.array.nav_drawer_feed_counter_values));
|
||||
final int selectedIndex = entryValues.indexOf("" + selected);
|
||||
|
||||
String[] items = context.getResources().getStringArray(R.array.nav_drawer_feed_counter_options);
|
||||
dialog.setSingleChoiceItems(items, selectedIndex, (d, which) -> {
|
||||
if (selectedIndex != which) {
|
||||
UserPreferences.setFeedCounterSetting(
|
||||
FeedCounter.fromOrdinal(Integer.parseInt(entryValues.get(which))));
|
||||
//Update subscriptions
|
||||
EventBus.getDefault().post(new UnreadItemsUpdateEvent());
|
||||
}
|
||||
d.dismiss();
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,7 @@ import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
public class FeedSortDialog {
|
||||
public static void showDialog(Context context) {
|
||||
MaterialAlertDialogBuilder dialog = new MaterialAlertDialogBuilder(context);
|
||||
dialog.setTitle(context.getString(R.string.pref_nav_drawer_feed_order_title));
|
||||
dialog.setTitle(context.getString(R.string.sort));
|
||||
dialog.setNegativeButton(android.R.string.cancel, (d, listener) -> d.dismiss());
|
||||
|
||||
int selected = UserPreferences.getFeedOrder().id;
|
||||
|
||||
@ -233,6 +233,9 @@ public class SubscriptionFragment extends Fragment
|
||||
} else if (itemId == R.id.subscriptions_sort) {
|
||||
FeedSortDialog.showDialog(requireContext());
|
||||
return true;
|
||||
} else if (itemId == R.id.subscriptions_counter) {
|
||||
FeedCounterDialog.showDialog(requireContext());
|
||||
return true;
|
||||
} else if (itemId == R.id.subscription_display_list) {
|
||||
setColumnNumber(1);
|
||||
return true;
|
||||
|
||||
@ -5,9 +5,11 @@ import android.content.Context;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.event.MessageEvent;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
public class AddToQueueSwipeAction implements SwipeAction {
|
||||
|
||||
@ -33,15 +35,20 @@ public class AddToQueueSwipeAction implements SwipeAction {
|
||||
|
||||
@Override
|
||||
public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
|
||||
if (!item.isTagged(FeedItem.TAG_QUEUE)) {
|
||||
DBWriter.addQueueItem(fragment.requireContext(), item);
|
||||
} else {
|
||||
if (item.isTagged(FeedItem.TAG_QUEUE)) {
|
||||
new RemoveFromQueueSwipeAction().performAction(item, fragment, filter);
|
||||
} else if (item.getMedia() == null) {
|
||||
EventBus.getDefault().post(new MessageEvent(fragment.getString(R.string.no_media_label)));
|
||||
} else {
|
||||
DBWriter.addQueueItem(fragment.requireContext(), item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willRemove(FeedItemFilter filter, FeedItem item) {
|
||||
return filter.showQueued || filter.showNew;
|
||||
if (item.getMedia() == null) {
|
||||
return false;
|
||||
}
|
||||
return filter.showNotQueued || filter.showNew;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,8 +4,10 @@ import android.content.Context;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.actionbutton.DownloadActionButton;
|
||||
import de.danoeh.antennapod.event.MessageEvent;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
public class StartDownloadSwipeAction implements SwipeAction {
|
||||
|
||||
@ -31,9 +33,12 @@ public class StartDownloadSwipeAction implements SwipeAction {
|
||||
|
||||
@Override
|
||||
public void performAction(FeedItem item, Fragment fragment, FeedItemFilter filter) {
|
||||
if (!item.isDownloaded() && !item.getFeed().isLocalFeed()) {
|
||||
new DownloadActionButton(item)
|
||||
.onClick(fragment.requireContext());
|
||||
if (item.getMedia() == null) {
|
||||
EventBus.getDefault().post(new MessageEvent(fragment.getString(R.string.no_media_label)));
|
||||
} else if (item.getFeed().isLocalFeed() || item.isDownloaded()) {
|
||||
EventBus.getDefault().post(new MessageEvent(fragment.getString(R.string.already_downloaded)));
|
||||
} else {
|
||||
new DownloadActionButton(item).onClick(fragment.requireContext());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -24,6 +24,10 @@
|
||||
android:id="@+id/subscriptions_sort"
|
||||
android:title="@string/sort"
|
||||
custom:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/subscriptions_counter"
|
||||
android:title="@string/pref_nav_drawer_feed_counter_title"
|
||||
custom:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/subscription_num_columns"
|
||||
android:title="@string/subscription_num_columns"
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.danoeh.antennapod.storage.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import de.danoeh.antennapod.event.FeedListUpdateEvent;
|
||||
import de.danoeh.antennapod.model.download.DownloadError;
|
||||
@ -45,38 +44,6 @@ public abstract class FeedDatabaseWriter {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a FeedItem by its identifying value.
|
||||
*/
|
||||
private static FeedItem searchFeedItemByIdentifyingValue(List<FeedItem> items, FeedItem searchItem) {
|
||||
for (FeedItem item : items) {
|
||||
if (TextUtils.equals(item.getIdentifyingValue(), searchItem.getIdentifyingValue())) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess if one of the items could actually mean the searched item, even if it uses another identifying value.
|
||||
* This is to work around podcasters breaking their GUIDs.
|
||||
*/
|
||||
private static FeedItem searchFeedItemGuessDuplicate(List<FeedItem> items, FeedItem searchItem) {
|
||||
// First, see if it is a well-behaving feed that contains an item with the same identifier
|
||||
for (FeedItem item : items) {
|
||||
if (FeedItemDuplicateGuesser.sameAndNotEmpty(item.getItemIdentifier(), searchItem.getItemIdentifier())) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
// Not found yet, start more expensive guessing
|
||||
for (FeedItem item : items) {
|
||||
if (FeedItemDuplicateGuesser.seemDuplicates(item, searchItem)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same
|
||||
* identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed.
|
||||
@ -108,6 +75,9 @@ public abstract class FeedDatabaseWriter {
|
||||
+ " already exists. Syncing new with existing one.");
|
||||
|
||||
Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
|
||||
FeedItemDuplicateGuesserPool newFeedDuplicateGuesser = new FeedItemDuplicateGuesserPool(newFeed.getItems());
|
||||
FeedItemDuplicateGuesserPool savedFeedDuplicateGuesser
|
||||
= new FeedItemDuplicateGuesserPool(savedFeed.getItems());
|
||||
|
||||
if (newFeed.getPageNr() == savedFeed.getPageNr()) {
|
||||
savedFeed.updateFromOther(newFeed);
|
||||
@ -128,7 +98,7 @@ public abstract class FeedDatabaseWriter {
|
||||
for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
|
||||
final FeedItem item = newFeed.getItems().get(idx);
|
||||
|
||||
FeedItem possibleDuplicate = searchFeedItemGuessDuplicate(newFeed.getItems(), item);
|
||||
FeedItem possibleDuplicate = newFeedDuplicateGuesser.guessDuplicate(item);
|
||||
if (!newFeed.isLocalFeed() && possibleDuplicate != null && item != possibleDuplicate) {
|
||||
// Canonical episode is the first one returned (usually oldest)
|
||||
DBWriter.addDownloadStatus(new DownloadResult(item.getTitle(),
|
||||
@ -142,9 +112,9 @@ public abstract class FeedDatabaseWriter {
|
||||
continue;
|
||||
}
|
||||
|
||||
FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed.getItems(), item);
|
||||
FeedItem oldItem = savedFeedDuplicateGuesser.findById(item);
|
||||
if (!newFeed.isLocalFeed() && oldItem == null) {
|
||||
oldItem = searchFeedItemGuessDuplicate(savedFeed.getItems(), item);
|
||||
oldItem = savedFeedDuplicateGuesser.guessDuplicate(item);
|
||||
if (oldItem != null) {
|
||||
Log.d(TAG, "Repaired duplicate: " + oldItem + ", " + item);
|
||||
DBWriter.addDownloadStatus(new DownloadResult(item.getTitle(),
|
||||
@ -181,6 +151,7 @@ public abstract class FeedDatabaseWriter {
|
||||
} else {
|
||||
savedFeed.getItems().add(idx, item);
|
||||
}
|
||||
savedFeedDuplicateGuesser.add(item);
|
||||
|
||||
boolean shouldPerformNewEpisodesAction = item.getPubDate() == null
|
||||
|| priorMostRecentDate == null
|
||||
@ -217,7 +188,7 @@ public abstract class FeedDatabaseWriter {
|
||||
Iterator<FeedItem> it = savedFeed.getItems().iterator();
|
||||
while (it.hasNext()) {
|
||||
FeedItem feedItem = it.next();
|
||||
if (searchFeedItemByIdentifyingValue(newFeed.getItems(), feedItem) == null) {
|
||||
if (newFeedDuplicateGuesser.findById(feedItem) == null) {
|
||||
unlistedItems.add(feedItem);
|
||||
it.remove();
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ public class FeedItemDuplicateGuesser {
|
||||
return sameAndNotEmpty(canonicalizeTitle(item1.getTitle()), canonicalizeTitle(item2.getTitle()));
|
||||
}
|
||||
|
||||
private static String canonicalizeTitle(String title) {
|
||||
public static String canonicalizeTitle(String title) {
|
||||
if (title == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
package de.danoeh.antennapod.storage.database;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class FeedItemDuplicateGuesserPool {
|
||||
private final Map<String, List<FeedItem>> normalizedTitles = new HashMap<>();
|
||||
private final Map<String, FeedItem> downloadUrls = new HashMap<>();
|
||||
private final Map<String, FeedItem> identifiers = new HashMap<>();
|
||||
|
||||
public FeedItemDuplicateGuesserPool(List<FeedItem> itemsList) {
|
||||
for (FeedItem item : itemsList) {
|
||||
add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(FeedItem item) {
|
||||
String normalizedTitle = FeedItemDuplicateGuesser.canonicalizeTitle(item.getTitle());
|
||||
if (!normalizedTitles.containsKey(normalizedTitle)) {
|
||||
normalizedTitles.put(normalizedTitle, new java.util.ArrayList<>());
|
||||
}
|
||||
normalizedTitles.get(normalizedTitle).add(item);
|
||||
if (item.getMedia() != null && !StringUtils.isEmpty(item.getMedia().getStreamUrl())
|
||||
&& !downloadUrls.containsKey(item.getMedia().getStreamUrl())) {
|
||||
downloadUrls.put(item.getMedia().getStreamUrl(), item);
|
||||
}
|
||||
if (item.getIdentifyingValue() != null && !identifiers.containsKey(item.getIdentifyingValue())) {
|
||||
identifiers.put(item.getIdentifyingValue(), item);
|
||||
}
|
||||
}
|
||||
|
||||
public FeedItem guessDuplicate(FeedItem searchItem) {
|
||||
if (searchItem.getMedia() != null && !StringUtils.isEmpty(searchItem.getMedia().getStreamUrl())
|
||||
&& downloadUrls.containsKey(searchItem.getMedia().getStreamUrl())) {
|
||||
return downloadUrls.get(searchItem.getMedia().getStreamUrl());
|
||||
}
|
||||
String normalizedTitle = FeedItemDuplicateGuesser.canonicalizeTitle(searchItem.getTitle());
|
||||
List<FeedItem> candidates = normalizedTitles.get(normalizedTitle);
|
||||
if (candidates == null) {
|
||||
return null;
|
||||
}
|
||||
for (FeedItem item : candidates) {
|
||||
if (FeedItemDuplicateGuesser.seemDuplicates(item, searchItem)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public FeedItem findById(FeedItem item) {
|
||||
if (identifiers.containsKey(item.getIdentifyingValue())) {
|
||||
return identifiers.get(item.getIdentifyingValue());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,33 +1,28 @@
|
||||
package de.danoeh.antennapod.net.download.service.episode.autodownload;
|
||||
package de.danoeh.antennapod.storage.database;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
|
||||
import de.danoeh.antennapod.model.download.DownloadError;
|
||||
import de.danoeh.antennapod.model.download.DownloadResult;
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedItemFilter;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.feed.SortOrder;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueue;
|
||||
import de.danoeh.antennapod.net.sync.serviceinterface.SynchronizationQueueStub;
|
||||
import de.danoeh.antennapod.storage.database.DBReader;
|
||||
import de.danoeh.antennapod.storage.database.DBWriter;
|
||||
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
|
||||
import de.danoeh.antennapod.storage.database.PodDBAdapter;
|
||||
import org.junit.After;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
|
||||
import de.danoeh.antennapod.storage.preferences.UserPreferences;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@ -35,40 +30,119 @@ import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Test class for {@link FeedDatabaseWriter}.
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class DbTasksTest {
|
||||
public class FeedDatabaseWriterTest {
|
||||
private Context context;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
context = RuntimeEnvironment.getApplication();
|
||||
UserPreferences.init(context);
|
||||
PlaybackPreferences.init(context);
|
||||
SynchronizationQueue.setInstance(new SynchronizationQueueStub());
|
||||
|
||||
// create new database
|
||||
PodDBAdapter.init(context);
|
||||
PodDBAdapter.deleteDatabase();
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
adapter.close();
|
||||
SynchronizationQueue.setInstance(new SynchronizationQueueStub());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
DBWriter.tearDownTests();
|
||||
PodDBAdapter.tearDownTests();
|
||||
@Test
|
||||
public void testStoreNewFeed() {
|
||||
Feed feed = createFeed();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
feed.getItems().add(createItem("item-" + i, "Item " + i, feed));
|
||||
}
|
||||
Feed updatedFeed = FeedDatabaseWriter.updateFeed(context, feed, false);
|
||||
List<FeedItem> storedItems = DBReader.getFeedItemList(updatedFeed, FeedItemFilter.unfiltered(),
|
||||
SortOrder.EPISODE_TITLE_A_Z, 0, Integer.MAX_VALUE);
|
||||
assertEquals(3, storedItems.size());
|
||||
for (int i = 0; i < 3; i++) {
|
||||
assertEquals("item-" + i, storedItems.get(i).getItemIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddItemsToExistingFeed() {
|
||||
Feed feed = createFeed();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
feed.getItems().add(createItem("item-" + i, "Item " + i, feed));
|
||||
}
|
||||
feed = FeedDatabaseWriter.updateFeed(context, feed, false);
|
||||
|
||||
Feed updatedFeed = createFeed();
|
||||
updatedFeed.setId(feed.getId());
|
||||
for (int i = 3; i < 6; i++) {
|
||||
updatedFeed.getItems().add(createItem("item-" + i, "Item " + i, feed));
|
||||
}
|
||||
FeedDatabaseWriter.updateFeed(context, updatedFeed, false);
|
||||
|
||||
List<FeedItem> dbItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(),
|
||||
SortOrder.EPISODE_TITLE_A_Z, 0, Integer.MAX_VALUE);
|
||||
assertEquals(6, dbItems.size());
|
||||
for (int i = 0; i < 6; i++) {
|
||||
assertEquals("item-" + i, dbItems.get(i).getItemIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddOrUpdateItems() throws ExecutionException, InterruptedException {
|
||||
Feed feed = createFeed();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
feed.getItems().add(createItem("item-" + i, "Item " + i, feed));
|
||||
}
|
||||
feed = FeedDatabaseWriter.updateFeed(context, feed, false);
|
||||
DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(),
|
||||
SortOrder.EPISODE_TITLE_A_Z, 0, Integer.MAX_VALUE);
|
||||
DBWriter.markItemPlayed(feed.getItems().get(2), FeedItem.PLAYED, false).get();
|
||||
|
||||
Feed updatedFeed = createFeed();
|
||||
updatedFeed.setId(feed.getId());
|
||||
for (int i = 2; i < 5; i++) {
|
||||
updatedFeed.getItems().add(createItem("item-" + i, "Item " + i, feed));
|
||||
}
|
||||
FeedDatabaseWriter.updateFeed(context, updatedFeed, false);
|
||||
|
||||
List<FeedItem> dbItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(),
|
||||
SortOrder.EPISODE_TITLE_A_Z, 0, Integer.MAX_VALUE);
|
||||
assertEquals(5, dbItems.size());
|
||||
for (int i = 0; i < 5; i++) {
|
||||
assertEquals("item-" + i, dbItems.get(i).getItemIdentifier());
|
||||
}
|
||||
assertEquals(FeedItem.PLAYED, dbItems.get(2).getPlayState());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDuplicateItemsInFeed() {
|
||||
Feed feed = createFeed();
|
||||
feed.getItems().add(createItem("id1", "Duplicate Title", feed));
|
||||
feed.getItems().add(createItem("id2", "Duplicate Title", feed));
|
||||
FeedDatabaseWriter.updateFeed(context, feed, false); // First update just takes the feed without complaining
|
||||
FeedDatabaseWriter.updateFeed(context, feed, false);
|
||||
|
||||
List<DownloadResult> downloadLog = DBReader.getDownloadLog();
|
||||
assertEquals(1, downloadLog.size());
|
||||
assertEquals(DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, downloadLog.get(0).getReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGuidUpdated() {
|
||||
Feed feed = createFeed();
|
||||
feed.getItems().add(createItem("old-id", "Unique Title", feed));
|
||||
FeedDatabaseWriter.updateFeed(context, feed, false);
|
||||
|
||||
Feed newFeed = createFeed();
|
||||
newFeed.getItems().add(createItem("new-id", "Unique Title", newFeed));
|
||||
Feed stored = FeedDatabaseWriter.updateFeed(context, newFeed, false);
|
||||
|
||||
assertEquals(1, stored.getItems().size());
|
||||
assertEquals("new-id", stored.getItems().get(0).getItemIdentifier());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateFeedNewFeed() {
|
||||
final int numItems = 10;
|
||||
|
||||
Feed feed = new Feed("url", null, "title");
|
||||
feed.setItems(new ArrayList<>());
|
||||
Feed feed = createFeed();
|
||||
for (int i = 0; i < numItems; i++) {
|
||||
feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i,
|
||||
new Date(), FeedItem.UNPLAYED, feed));
|
||||
@ -86,12 +160,9 @@ public class DbTasksTest {
|
||||
/** Two feeds with the same title, but different download URLs should be treated as different feeds. */
|
||||
@Test
|
||||
public void testUpdateFeedSameTitle() {
|
||||
|
||||
Feed feed1 = new Feed("url1", null, "title");
|
||||
Feed feed2 = new Feed("url2", null, "title");
|
||||
|
||||
feed1.setItems(new ArrayList<>());
|
||||
feed2.setItems(new ArrayList<>());
|
||||
Feed feed1 = createFeed();
|
||||
Feed feed2 = createFeed();
|
||||
feed2.setDownloadUrl("different url");
|
||||
|
||||
Feed savedFeed1 = FeedDatabaseWriter.updateFeed(context, feed1, false);
|
||||
Feed savedFeed2 = FeedDatabaseWriter.updateFeed(context, feed2, false);
|
||||
@ -104,8 +175,7 @@ public class DbTasksTest {
|
||||
final int numItemsOld = 10;
|
||||
final int numItemsNew = 10;
|
||||
|
||||
final Feed feed = new Feed("url", null, "title");
|
||||
feed.setItems(new ArrayList<>());
|
||||
final Feed feed = createFeed();
|
||||
for (int i = 0; i < numItemsOld; i++) {
|
||||
feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i,
|
||||
new Date(i), FeedItem.PLAYED, feed));
|
||||
@ -144,9 +214,9 @@ public class DbTasksTest {
|
||||
|
||||
@Test
|
||||
public void testUpdateFeedMediaUrlResetState() {
|
||||
final Feed feed = new Feed("url", null, "title");
|
||||
final Feed feed = createFeed();
|
||||
FeedItem item = new FeedItem(0, "item", "id", "link", new Date(), FeedItem.PLAYED, feed);
|
||||
feed.setItems(singletonList(item));
|
||||
feed.setItems(Collections.singletonList(item));
|
||||
|
||||
PodDBAdapter adapter = PodDBAdapter.getInstance();
|
||||
adapter.open();
|
||||
@ -173,8 +243,7 @@ public class DbTasksTest {
|
||||
|
||||
@Test
|
||||
public void testUpdateFeedRemoveUnlistedItems() {
|
||||
final Feed feed = new Feed("url", null, "title");
|
||||
feed.setItems(new ArrayList<>());
|
||||
final Feed feed = createFeed();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
feed.getItems().add(
|
||||
new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(i), FeedItem.PLAYED, feed));
|
||||
@ -195,8 +264,7 @@ public class DbTasksTest {
|
||||
|
||||
@Test
|
||||
public void testUpdateFeedSetDuplicate() {
|
||||
final Feed feed = new Feed("url", null, "title");
|
||||
feed.setItems(new ArrayList<>());
|
||||
final Feed feed = createFeed();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
FeedItem item =
|
||||
new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(i), FeedItem.PLAYED, feed);
|
||||
@ -249,4 +317,19 @@ public class DbTasksTest {
|
||||
lastDate = item.getPubDate();
|
||||
}
|
||||
}
|
||||
|
||||
private Feed createFeed() {
|
||||
Feed feed = new Feed("url", null, null);
|
||||
feed.setItems(new ArrayList<>());
|
||||
return feed;
|
||||
}
|
||||
|
||||
private FeedItem createItem(String identifier, String title, Feed feed) {
|
||||
FeedItem item = new FeedItem();
|
||||
item.setItemIdentifier(identifier);
|
||||
item.setTitle(title);
|
||||
item.setMedia(new FeedMedia(item, "url-" + title, 2, "mime"));
|
||||
item.setFeed(feed);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
package de.danoeh.antennapod.storage.database;
|
||||
|
||||
import de.danoeh.antennapod.model.feed.Feed;
|
||||
import de.danoeh.antennapod.model.feed.FeedItem;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class FeedItemDuplicateGuesserPoolTest {
|
||||
|
||||
@Test
|
||||
public void testDuplicateIsConsistent() {
|
||||
Feed feed = new Feed("url", null, null);
|
||||
FeedItem item1 = createItem("id1", "Title", feed);
|
||||
FeedItem item2 = createItem("id2", "Title", feed);
|
||||
|
||||
FeedItemDuplicateGuesserPool pool = new FeedItemDuplicateGuesserPool(new ArrayList<>());
|
||||
pool.add(item1);
|
||||
assertSame(item1, pool.guessDuplicate(item1));
|
||||
assertSame(item1, pool.guessDuplicate(item2));
|
||||
pool.add(item2);
|
||||
assertSame(item1, pool.guessDuplicate(item1));
|
||||
assertSame(item1, pool.guessDuplicate(item2));
|
||||
}
|
||||
|
||||
private FeedItem createItem(String identifier, String title, Feed feed) {
|
||||
FeedItem item = new FeedItem();
|
||||
item.setItemIdentifier(identifier);
|
||||
item.setTitle(title);
|
||||
item.setMedia(new FeedMedia(item, "url-" + title, 2, "mime"));
|
||||
item.setFeed(feed);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
@ -278,6 +278,10 @@ public abstract class UserPreferences {
|
||||
return FeedCounter.fromOrdinal(Integer.parseInt(value));
|
||||
}
|
||||
|
||||
public static void setFeedCounterSetting(FeedCounter counter) {
|
||||
prefs.edit().putString(PREF_DRAWER_FEED_COUNTER, "" + counter.id).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if episodes should use their own cover, {@code false} otherwise
|
||||
*/
|
||||
|
||||
@ -345,6 +345,7 @@
|
||||
<string name="confirm_mobile_streaming_notification_message">Streaming over mobile data connection is disabled in the settings. Tap to stream anyway.</string>
|
||||
<string name="confirm_mobile_streaming_button_always">Always</string>
|
||||
<string name="confirm_mobile_streaming_button_once">Once</string>
|
||||
<string name="already_downloaded">Episode is already downloaded</string>
|
||||
|
||||
<!-- Mediaplayer messages -->
|
||||
<string name="playback_error_generic">The media file could not be played.\n\n- Try deleting and re-downloading the episode.\n- Check your network connection, and make sure no VPN or login page is blocking access.\n- Try long-pressing and sharing the \"Media address\" to your web browser to see if it can be played there. If not, contact the podcast creators.</string>
|
||||
@ -489,10 +490,7 @@
|
||||
<string name="bottom_navigation_summary">Access the most important screens from everywhere, in a single tap</string>
|
||||
<string name="pref_nav_drawer_items_title">Customize navigation</string>
|
||||
<string name="pref_nav_drawer_items_sum">Change which items appear in the navigation drawer or bottom navigation</string>
|
||||
<string name="pref_nav_drawer_feed_order_title">Set subscription order</string>
|
||||
<string name="pref_nav_drawer_feed_order_sum">Change the order of your subscriptions</string>
|
||||
<string name="pref_nav_drawer_feed_counter_title">Set subscription counter</string>
|
||||
<string name="pref_nav_drawer_feed_counter_sum">Change the information displayed by the subscription counter. Also affects the sorting of subscriptions if \'Subscription Order\' is set to \'Counter\'.</string>
|
||||
<string name="pref_nav_drawer_feed_counter_title">Counter</string>
|
||||
<string name="pref_automatic_download_title">Automatic download</string>
|
||||
<string name="pref_automatic_download_global_description">Automatically download episodes from the inbox. Can be overridden per podcast.</string>
|
||||
<string name="pref_automatic_download_queue_title">Download queued</string>
|
||||
@ -561,8 +559,6 @@
|
||||
<string name="pref_delete_removes_from_queue_sum">Automatically remove an episode from the queue when it is deleted</string>
|
||||
<string name="pref_downloads_button_action_title">Play from downloads screen</string>
|
||||
<string name="pref_downloads_button_action_sum">Display play button instead of delete button on downloads screen</string>
|
||||
<string name="pref_filter_feed_title">Subscription filter</string>
|
||||
<string name="pref_filter_feed_sum">Filter your subscriptions in navigation drawer and subscriptions screen</string>
|
||||
<string name="subscriptions_counter_greater_zero">Counter greater than zero</string>
|
||||
<string name="auto_downloaded">Auto downloaded</string>
|
||||
<string name="not_auto_downloaded">Not auto downloaded</string>
|
||||
@ -678,7 +674,7 @@
|
||||
<string name="synchronization_summary_unchoosen">You can choose from multiple providers to synchronize your subscriptions and episode play state with</string>
|
||||
<string name="dialog_choose_sync_service_title">Choose synchronization provider</string>
|
||||
<string name="gpodnet_description">Gpodder.net is an open-source podcast synchronization service that you can install on your own server. Gpodder.net is independent of the AntennaPod project.</string>
|
||||
<string name="synchronization_summary_nextcloud">Gpoddersync is an open-source Nextcloud app that you can easily install on your own server. The app is independent of the AntennaPod project.</string>
|
||||
<string name="synchronization_summary_nextcloud">Gpodder Sync is an open-source Nextcloud app that you can easily install on your own server. Gpodder Sync is independent of the AntennaPod project.</string>
|
||||
<string name="synchronization_host_explanation">You can pick your own server to synchronize with. When you have identified your preferred synchronization server, please enter its address here.</string>
|
||||
<string name="synchronization_host_label">Server address</string>
|
||||
<string name="proceed_to_login_butLabel">Proceed to login</string>
|
||||
@ -709,7 +705,7 @@
|
||||
<string name="gpodnetsync_pref_report_successful">Successful</string>
|
||||
<string name="gpodnetsync_pref_report_failed">Failed</string>
|
||||
<string name="gpodnetsync_username_characters_error">Usernames may only contain letters, digits, hyphens and underscores.</string>
|
||||
<string name="nextcloud_login_error_generic">Unable to log into your Nextcloud.\n\n- Check your network connection.\n- Confirm that you are using the correct server address.\n- Make sure that the gpoddersync Nextcloud plugin is installed.</string>
|
||||
<string name="nextcloud_login_error_generic">Unable to log into your Nextcloud.\n\n- Check your network connection.\n- Confirm that you are using the correct server address.\n- Make sure that the Gpodder Sync Nextcloud plugin is installed.</string>
|
||||
|
||||
<!-- Directory chooser -->
|
||||
<string name="choose_data_directory">Choose data folder</string>
|
||||
|
||||
@ -17,23 +17,6 @@
|
||||
android:summary="@string/pref_tinted_theme_message"
|
||||
android:defaultValue="false" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/subscriptions_label">
|
||||
<Preference
|
||||
android:title="@string/pref_nav_drawer_feed_order_title"
|
||||
android:key="prefDrawerFeedOrder"
|
||||
android:summary="@string/pref_nav_drawer_feed_order_sum"/>
|
||||
<de.danoeh.antennapod.ui.preferences.preference.MaterialListPreference
|
||||
android:entryValues="@array/nav_drawer_feed_counter_values"
|
||||
android:entries="@array/nav_drawer_feed_counter_options"
|
||||
android:title="@string/pref_nav_drawer_feed_counter_title"
|
||||
android:key="prefDrawerFeedIndicator"
|
||||
android:summary="@string/pref_nav_drawer_feed_counter_sum"
|
||||
android:defaultValue="1"/>
|
||||
<Preference
|
||||
android:title="@string/pref_filter_feed_title"
|
||||
android:key="prefSubscriptionsFilter"
|
||||
android:summary="@string/pref_filter_feed_sum" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/episode_information">
|
||||
<SwitchPreferenceCompat
|
||||
android:title="@string/pref_episode_cover_title"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user