summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md4
-rw-r--r--app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java17
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/GenerativePlaceholderImageModelLoader.java139
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java4
-rw-r--r--core/src/main/res/raw/local_feed_default_icon.pngbin1240 -> 0 bytes
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java28
-rw-r--r--model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java1
-rw-r--r--parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/util/MimeTypeUtils.java2
11 files changed, 171 insertions, 38 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e3bc7269d..0017c897d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,7 @@ How to report a bug
- If possible, add instructions on how to reproduce the bug.
- If possible, add a logfile to your post. This is especially useful if the bug makes the application crash. AntennaPod has an `export logs` feature for this.
- Usually, you can take a screenshot of your smartphone by pressing *Power* + *Volume down* for a few seconds.
-- Please use the following **[template](.github/ISSUE_TEMPLATE/bug_report.md)**.
+- Please use the following **[template](https://github.com/AntennaPod/AntennaPod/issues/new?assignees=&labels=Type%3A+Possible+bug&template=bug_report.yml)**.
How to submit a feature request
@@ -18,7 +18,7 @@ How to submit a feature request
- To make it easier for us to keep track of requests, please only make one feature request per issue.
- Give a brief explanation about the problem that may currently exist and how your requested feature solves this problem.
- Try to be as specific as possible. Please not only explain what the feature does, but also how. If your request is about (or includes) changing or extending the UI, describe what the UI would look like and how the user would interact with it.
-- Please use the following **[template](.github/ISSUE_TEMPLATE/feature_request.md)**.
+- Please use the following **[template](https://github.com/AntennaPod/AntennaPod/issues/new?assignees=&labels=&template=feature_request.yml)**.
Translating AntennaPod
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java
index 5ab354d05..8351d1fb5 100644
--- a/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java
+++ b/app/src/main/java/de/danoeh/antennapod/adapter/SubscriptionsRecyclerAdapter.java
@@ -33,7 +33,6 @@ import java.util.Locale;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
-import de.danoeh.antennapod.core.feed.LocalFeedUpdater;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.NavDrawerData;
import de.danoeh.antennapod.fragment.FeedItemlistFragment;
@@ -255,7 +254,7 @@ public class SubscriptionsRecyclerAdapter extends SelectableAdapter<Subscription
if (drawerItem.type == NavDrawerData.DrawerItem.Type.FEED) {
Feed feed = ((NavDrawerData.FeedDrawerItem) drawerItem).feed;
boolean textAndImageCombind = feed.isLocalFeed()
- && LocalFeedUpdater.getDefaultIconUrl(itemView.getContext()).equals(feed.getImageUrl());
+ && feed.getImageUrl() != null && feed.getImageUrl().startsWith(Feed.PREFIX_GENERATIVE_COVER);
new CoverLoader(mainActivityRef.get())
.withUri(feed.getImageUrl())
.withPlaceholderView(feedTitle, textAndImageCombind)
diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
index 120d1def8..ee56bb9f9 100644
--- a/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
+++ b/app/src/main/java/de/danoeh/antennapod/fragment/AddFeedFragment.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.fragment;
import android.content.ActivityNotFoundException;
+import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
@@ -140,9 +141,12 @@ public class AddFeedFragment extends Fragment {
alertViewBinding.urlEditText.setHint(R.string.add_podcast_by_url_hint);
ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- String clipboardContent = clipboard.getText() != null ? clipboard.getText().toString() : "";
- if (clipboardContent.trim().startsWith("http")) {
- alertViewBinding.urlEditText.setText(clipboardContent.trim());
+ final ClipData clipData = clipboard.getPrimaryClip();
+ if (clipData != null && clipData.getItemCount() > 0 && clipData.getItemAt(0).getText() != null) {
+ final String clipboardContent = clipData.getItemAt(0).getText().toString();
+ if (clipboardContent.trim().startsWith("http")) {
+ alertViewBinding.urlEditText.setText(clipboardContent.trim());
+ }
}
builder.setView(alertViewBinding.getRoot());
builder.setPositiveButton(R.string.confirm_label,
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java
index 7ddaa080a..e0e1bbaa5 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java
@@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.feed;
-import android.content.ContentResolver;
import android.content.Context;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
@@ -104,7 +103,7 @@ public class LocalFeedUpdater {
}
}
- feed.setImageUrl(getImageUrl(context, documentFolder));
+ feed.setImageUrl(getImageUrl(documentFolder));
feed.getPreferences().setAutoDownload(false);
feed.getPreferences().setAutoDeleteAction(FeedPreferences.AutoDeleteAction.NO);
@@ -122,7 +121,7 @@ public class LocalFeedUpdater {
* Returns the image URL for the local feed.
*/
@NonNull
- static String getImageUrl(@NonNull Context context, @NonNull DocumentFile documentFolder) {
+ static String getImageUrl(@NonNull DocumentFile documentFolder) {
// look for special file names
for (String iconLocation : PREFERRED_FEED_IMAGE_FILENAMES) {
DocumentFile image = documentFolder.findFile(iconLocation);
@@ -140,17 +139,7 @@ public class LocalFeedUpdater {
}
// use default icon as fallback
- return getDefaultIconUrl(context);
- }
-
- /**
- * Returns the URL of the default icon for a local feed. The URL refers to an app resource file.
- */
- public static String getDefaultIconUrl(Context context) {
- String resourceEntryName = context.getResources().getResourceEntryName(R.raw.local_feed_default_icon);
- return ContentResolver.SCHEME_ANDROID_RESOURCE + "://"
- + context.getPackageName() + "/raw/"
- + resourceEntryName;
+ return Feed.PREFIX_GENERATIVE_COVER + documentFolder.getUri();
}
private static FeedItem feedContainsFile(Feed feed, String filename) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java
index 797addcc1..593b683f7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java
@@ -42,6 +42,7 @@ public class ApGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
registry.replace(String.class, InputStream.class, new MetadataRetrieverLoader.Factory(context));
+ registry.append(String.class, InputStream.class, new GenerativePlaceholderImageModelLoader.Factory());
registry.append(String.class, InputStream.class, new ApOkHttpUrlLoader.Factory());
registry.append(String.class, InputStream.class, new NoHttpStringLoader.StreamFactory());
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/GenerativePlaceholderImageModelLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/GenerativePlaceholderImageModelLoader.java
new file mode 100644
index 000000000..a2263bc28
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/GenerativePlaceholderImageModelLoader.java
@@ -0,0 +1,139 @@
+package de.danoeh.antennapod.core.glide;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.LinearGradient;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Shader;
+import androidx.annotation.NonNull;
+import com.bumptech.glide.Priority;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.Options;
+import com.bumptech.glide.load.data.DataFetcher;
+import com.bumptech.glide.load.model.ModelLoader;
+import com.bumptech.glide.load.model.ModelLoaderFactory;
+import com.bumptech.glide.load.model.MultiModelLoaderFactory;
+import com.bumptech.glide.signature.ObjectKey;
+import de.danoeh.antennapod.model.feed.Feed;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.Random;
+
+public final class GenerativePlaceholderImageModelLoader implements ModelLoader<String, InputStream> {
+
+ public static class Factory implements ModelLoaderFactory<String, InputStream> {
+ @NonNull
+ @Override
+ public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory unused) {
+ return new GenerativePlaceholderImageModelLoader();
+ }
+
+ @Override
+ public void teardown() {
+ // Do nothing.
+ }
+ }
+
+ @Override
+ public LoadData<InputStream> buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) {
+ return new LoadData<>(new ObjectKey(model), new EmbeddedImageFetcher(model, width, height));
+ }
+
+ @Override
+ public boolean handles(@NonNull String model) {
+ return model.startsWith(Feed.PREFIX_GENERATIVE_COVER);
+ }
+
+ static class EmbeddedImageFetcher implements DataFetcher<InputStream> {
+ private static final int[] PALETTES = {0xff78909c, 0xffff6f00, 0xff388e3c,
+ 0xff00838f, 0xff7b1fa2, 0xffb71c1c, 0xff2196f3};
+ private final String model;
+ private final int width;
+ private final int height;
+
+ public EmbeddedImageFetcher(String model, int width, int height) {
+ this.model = model;
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ final Random generator = new Random(model.hashCode());
+ final int lineGridSteps = 4 + generator.nextInt(4);
+ final int slope = width / 4;
+ final float shadowWidth = width * 0.01f;
+ final float lineDistance = ((float) width / (lineGridSteps - 2));
+ final int baseColor = PALETTES[generator.nextInt(PALETTES.length)];
+
+ Paint paint = new Paint();
+ int color = randomShadeOfGrey(generator);
+ paint.setColor(color);
+ paint.setStrokeWidth(lineDistance);
+ paint.setColorFilter(new PorterDuffColorFilter(baseColor, PorterDuff.Mode.MULTIPLY));
+ Paint paintShadow = new Paint();
+ paintShadow.setColor(0xff000000);
+ paintShadow.setStrokeWidth(lineDistance);
+
+ int forcedColorChange = 1 + generator.nextInt(lineGridSteps - 2);
+ for (int i = lineGridSteps - 1; i >= 0; i--) {
+ float linePos = (i - 0.5f) * lineDistance;
+ boolean switchColor = generator.nextFloat() < 0.3f || i == forcedColorChange;
+ if (switchColor) {
+ int newColor = color;
+ while (newColor == color) {
+ newColor = randomShadeOfGrey(generator);
+ }
+ color = newColor;
+ paint.setColor(newColor);
+ canvas.drawLine(linePos + slope + shadowWidth, -slope,
+ linePos - slope + shadowWidth, height + slope, paintShadow);
+ }
+ canvas.drawLine(linePos + slope, -slope,
+ linePos - slope, height + slope, paint);
+ }
+
+ Paint gradientPaint = new Paint();
+ paint.setDither(true);
+ gradientPaint.setShader(new LinearGradient(0, 0, 0, height, 0x00000000, 0x55000000, Shader.TileMode.CLAMP));
+ canvas.drawRect(0, 0, width, height, gradientPaint);
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
+ InputStream is = new ByteArrayInputStream(baos.toByteArray());
+ callback.onDataReady(is);
+ }
+
+ private static int randomShadeOfGrey(Random generator) {
+ return 0xff777777 + 0x222222 * generator.nextInt(5);
+ }
+
+ @Override
+ public void cleanup() {
+ // nothing to clean up
+ }
+
+ @Override
+ public void cancel() {
+ // cannot cancel
+ }
+
+ @NonNull
+ @Override
+ public Class<InputStream> getDataClass() {
+ return InputStream.class;
+ }
+
+ @NonNull
+ @Override
+ public DataSource getDataSource() {
+ return DataSource.LOCAL;
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
index 21d3452d6..7e3b07880 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.core.service.download.handler;
+import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
@@ -48,6 +49,9 @@ public class FeedParserTask implements Callable<FeedHandlerResult> {
result = feedHandler.parseFeed(feed);
Log.d(TAG, feed.getTitle() + " parsed");
checkFeedData(feed);
+ if (TextUtils.isEmpty(feed.getImageUrl())) {
+ feed.setImageUrl(Feed.PREFIX_GENERATIVE_COVER + feed.getDownload_url());
+ }
} catch (SAXException | IOException | ParserConfigurationException e) {
successful = false;
e.printStackTrace();
diff --git a/core/src/main/res/raw/local_feed_default_icon.png b/core/src/main/res/raw/local_feed_default_icon.png
deleted file mode 100644
index c1b24a729..000000000
--- a/core/src/main/res/raw/local_feed_default_icon.png
+++ /dev/null
Binary files differ
diff --git a/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
index eb56a1876..37d525670 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
@@ -31,7 +31,6 @@ import java.util.List;
import de.danoeh.antennapod.core.ApplicationCallbacks;
import de.danoeh.antennapod.core.ClientConfig;
-import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
@@ -41,6 +40,7 @@ import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -158,8 +158,7 @@ public class LocalFeedUpdaterTest {
callUpdateFeed(LOCAL_FEED_DIR1);
Feed feedAfter = verifySingleFeedInDatabase();
- String resourceEntryName = context.getResources().getResourceEntryName(R.raw.local_feed_default_icon);
- assertThat(feedAfter.getImageUrl(), endsWith(resourceEntryName));
+ assertThat(feedAfter.getImageUrl(), startsWith(Feed.PREFIX_GENERATIVE_COVER));
}
/**
@@ -191,17 +190,15 @@ public class LocalFeedUpdaterTest {
@Test
public void testGetImageUrl_EmptyFolder() {
DocumentFile documentFolder = mockDocumentFolder();
- String imageUrl = LocalFeedUpdater.getImageUrl(context, documentFolder);
- String defaultImageName = context.getResources().getResourceEntryName(R.raw.local_feed_default_icon);
- assertThat(imageUrl, endsWith(defaultImageName));
+ String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ assertThat(imageUrl, startsWith(Feed.PREFIX_GENERATIVE_COVER));
}
@Test
public void testGetImageUrl_NoImageButAudioFiles() {
DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"));
- String imageUrl = LocalFeedUpdater.getImageUrl(context, documentFolder);
- String defaultImageName = context.getResources().getResourceEntryName(R.raw.local_feed_default_icon);
- assertThat(imageUrl, endsWith(defaultImageName));
+ String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ assertThat(imageUrl, startsWith(Feed.PREFIX_GENERATIVE_COVER));
}
@Test
@@ -209,7 +206,7 @@ public class LocalFeedUpdaterTest {
for (String filename : LocalFeedUpdater.PREFERRED_FEED_IMAGE_FILENAMES) {
DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile(filename, "image/jpeg")); // image MIME type doesn't matter
- String imageUrl = LocalFeedUpdater.getImageUrl(context, documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
assertThat(imageUrl, endsWith(filename));
}
}
@@ -218,7 +215,7 @@ public class LocalFeedUpdaterTest {
public void testGetImageUrl_OtherImageFilenameJpg() {
DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile("my-image.jpg", "image/jpeg"));
- String imageUrl = LocalFeedUpdater.getImageUrl(context, documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
assertThat(imageUrl, endsWith("my-image.jpg"));
}
@@ -226,7 +223,7 @@ public class LocalFeedUpdaterTest {
public void testGetImageUrl_OtherImageFilenameJpeg() {
DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile("my-image.jpeg", "image/jpeg"));
- String imageUrl = LocalFeedUpdater.getImageUrl(context, documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
assertThat(imageUrl, endsWith("my-image.jpeg"));
}
@@ -234,7 +231,7 @@ public class LocalFeedUpdaterTest {
public void testGetImageUrl_OtherImageFilenamePng() {
DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile("my-image.png", "image/png"));
- String imageUrl = LocalFeedUpdater.getImageUrl(context, documentFolder);
+ String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
assertThat(imageUrl, endsWith("my-image.png"));
}
@@ -242,9 +239,8 @@ public class LocalFeedUpdaterTest {
public void testGetImageUrl_OtherImageFilenameUnsupportedMimeType() {
DocumentFile documentFolder = mockDocumentFolder(mockDocumentFile("audio.mp3", "audio/mp3"),
mockDocumentFile("my-image.svg", "image/svg+xml"));
- String imageUrl = LocalFeedUpdater.getImageUrl(context, documentFolder);
- String defaultImageName = context.getResources().getResourceEntryName(R.raw.local_feed_default_icon);
- assertThat(imageUrl, endsWith(defaultImageName));
+ String imageUrl = LocalFeedUpdater.getImageUrl(documentFolder);
+ assertThat(imageUrl, startsWith(Feed.PREFIX_GENERATIVE_COVER));
}
/**
diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java b/model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java
index e570f9bce..006505eb1 100644
--- a/model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java
+++ b/model/src/main/java/de/danoeh/antennapod/model/feed/Feed.java
@@ -19,6 +19,7 @@ public class Feed extends FeedFile {
public static final String TYPE_RSS2 = "rss";
public static final String TYPE_ATOM1 = "atom";
public static final String PREFIX_LOCAL_FOLDER = "antennapod_local:";
+ public static final String PREFIX_GENERATIVE_COVER = "antennapod_generative_cover:";
/**
* title as defined by the feed.
diff --git a/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/util/MimeTypeUtils.java b/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/util/MimeTypeUtils.java
index be1048050..99faaa133 100644
--- a/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/util/MimeTypeUtils.java
+++ b/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/util/MimeTypeUtils.java
@@ -16,7 +16,7 @@ public class MimeTypeUtils {
// based on https://developer.android.com/guide/topics/media/media-formats
static final Set<String> AUDIO_FILE_EXTENSIONS = new HashSet<>(Arrays.asList(
- "3gp", "aac", "amr", "flac", "imy", "m4a", "mid", "mkv", "mp3", "mp4", "mxmf", "oga",
+ "3gp", "aac", "amr", "flac", "imy", "m4a", "m4b", "mid", "mkv", "mp3", "mp4", "mxmf", "oga",
"ogg", "ogx", "opus", "ota", "rtttl", "rtx", "wav", "xmf"
));