thumbnails) {
if (DEBUG) {
Log.d(TAG, "Thumbnail - loadCurrentThumbnail() called with thumbnails = ["
+ thumbnails.size() + "]");
}
- // first cancel any previous loading
- cancelLoadingCurrentThumbnail();
+ // Cancel any ongoing image loading
+ if (thumbnailDisposable != null) {
+ thumbnailDisposable.dispose();
+ }
// Unset currentThumbnail, since it is now outdated. This ensures it is not used in media
- // session metadata while the new thumbnail is being loaded by Picasso.
+ // session metadata while the new thumbnail is being loaded by Coil.
onThumbnailLoaded(null);
if (thumbnails.isEmpty()) {
return;
}
// scale down the notification thumbnail for performance
- PicassoHelper.loadScaledDownThumbnail(context, thumbnails)
- .tag(PICASSO_PLAYER_THUMBNAIL_TAG)
- .into(currentThumbnailTarget);
+ final var thumbnailTarget = new Target() {
+ @Override
+ public void onError(@Nullable final coil3.Image error) {
+ Log.e(TAG, "Thumbnail - onError() called");
+ // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too.
+ onThumbnailLoaded(null);
+ }
+
+ @Override
+ public void onStart(@Nullable final coil3.Image placeholder) {
+ if (DEBUG) {
+ Log.d(TAG, "Thumbnail - onStart() called");
+ }
+ }
+
+ @Override
+ public void onSuccess(@NonNull final coil3.Image result) {
+ if (DEBUG) {
+ Log.d(TAG, "Thumbnail - onSuccess() called with: drawable = [" + result + "]");
+ }
+ // there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too.
+ onThumbnailLoaded(toBitmap(result));
+ }
+ };
+ thumbnailDisposable = CoilHelper.INSTANCE
+ .loadScaledDownThumbnail(context, thumbnails, thumbnailTarget);
}
- private void cancelLoadingCurrentThumbnail() {
- // cancel the Picasso job associated with the player thumbnail, if any
- PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG);
- }
private void onThumbnailLoaded(@Nullable final Bitmap bitmap) {
// Avoid useless thumbnail updates, if the thumbnail has not actually changed. Based on the
// thumbnail loading code, this if would be skipped only when both bitmaps are `null`, since
- // onThumbnailLoaded won't be called twice with the same nonnull bitmap by Picasso's target.
+ // onThumbnailLoaded won't be called twice with the same nonnull bitmap by Coil's target.
if (currentThumbnail != bitmap) {
currentThumbnail = bitmap;
UIs.call(playerUi -> playerUi.onThumbnailLoaded(bitmap));
diff --git a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
index 066f92c26..8994aef79 100644
--- a/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/player/playqueue/PlayQueueItemBuilder.java
@@ -6,8 +6,8 @@ import android.view.MotionEvent;
import android.view.View;
import org.schabi.newpipe.util.Localization;
-import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.ServiceHelper;
+import org.schabi.newpipe.util.image.CoilHelper;
public class PlayQueueItemBuilder {
private static final String TAG = PlayQueueItemBuilder.class.toString();
@@ -33,7 +33,7 @@ public class PlayQueueItemBuilder {
holder.itemDurationView.setVisibility(View.GONE);
}
- PicassoHelper.loadThumbnail(item.getThumbnails()).into(holder.itemThumbnailView);
+ CoilHelper.INSTANCE.loadThumbnail(holder.itemThumbnailView, item.getThumbnails());
holder.itemRoot.setOnClickListener(view -> {
if (onItemClickListener != null) {
diff --git a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java
index 09c61b8b3..d9e25fe8b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/player/seekbarpreview/SeekbarPreviewThumbnailHolder.java
@@ -13,8 +13,9 @@ import androidx.collection.SparseArrayCompat;
import com.google.common.base.Stopwatch;
+import org.schabi.newpipe.App;
import org.schabi.newpipe.extractor.stream.Frameset;
-import org.schabi.newpipe.util.image.PicassoHelper;
+import org.schabi.newpipe.util.image.CoilHelper;
import java.util.Comparator;
import java.util.List;
@@ -207,8 +208,8 @@ public class SeekbarPreviewThumbnailHolder {
Log.d(TAG, "Downloading bitmap for seekbarPreview from '" + url + "'");
// Gets the bitmap within the timeout of 15 seconds imposed by default by OkHttpClient
- // Ensure that your are not running on the main-Thread this will otherwise hang
- final Bitmap bitmap = PicassoHelper.loadSeekbarThumbnailPreview(url).get();
+ // Ensure that you are not running on the main thread, otherwise this will hang
+ final var bitmap = CoilHelper.INSTANCE.loadBitmapBlocking(App.getInstance(), url);
if (sw != null) {
Log.d(TAG, "Download of bitmap for seekbarPreview from '" + url + "' took "
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
index b855f7c38..85ee97853 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -19,12 +19,12 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.player.helper.PlayerHelper;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.image.ImageStrategy;
-import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.image.PreferredImageQuality;
-import java.io.IOException;
import java.util.Locale;
+import coil3.SingletonImageLoader;
+
public class ContentSettingsFragment extends BasePreferenceFragment {
private String youtubeRestrictedModeEnabledKey;
@@ -74,14 +74,12 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
(preference, newValue) -> {
ImageStrategy.setPreferredImageQuality(PreferredImageQuality
.fromPreferenceKey(requireContext(), (String) newValue));
- try {
- PicassoHelper.clearCache(preference.getContext());
- Toast.makeText(preference.getContext(),
- R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
+ final var loader = SingletonImageLoader.get(preference.getContext());
+ loader.getMemoryCache().clear();
+ loader.getDiskCache().clear();
+ Toast.makeText(preference.getContext(),
+ R.string.thumbnail_cache_wipe_complete_notice, Toast.LENGTH_SHORT)
.show();
- } catch (final IOException e) {
- Log.e(TAG, "Unable to clear Picasso cache", e);
- }
return true;
});
}
diff --git a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
index 82f2f5bb6..229de7005 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/DebugSettingsFragment.java
@@ -10,7 +10,6 @@ import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.feed.notifications.NotificationWorker;
-import org.schabi.newpipe.util.image.PicassoHelper;
import java.util.Optional;
@@ -25,8 +24,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
requirePreference(R.string.allow_heap_dumping_key);
final Preference showMemoryLeaksPreference =
requirePreference(R.string.show_memory_leaks_key);
- final Preference showImageIndicatorsPreference =
- requirePreference(R.string.show_image_indicators_key);
final Preference checkNewStreamsPreference =
requirePreference(R.string.check_new_streams_key);
final Preference crashTheAppPreference =
@@ -54,11 +51,6 @@ public class DebugSettingsFragment extends BasePreferenceFragment {
showMemoryLeaksPreference.setSummary(R.string.leak_canary_not_available);
}
- showImageIndicatorsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
- PicassoHelper.setIndicatorsEnabled((Boolean) newValue);
- return true;
- });
-
checkNewStreamsPreference.setOnPreferenceClickListener(preference -> {
NotificationWorker.runNow(preference.getContext());
return true;
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
index 18e0816bb..25d6b3a0f 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectChannelFragment.java
@@ -19,8 +19,8 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.database.subscription.SubscriptionEntity;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
-import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.ThemeHelper;
+import org.schabi.newpipe.util.image.CoilHelper;
import java.util.List;
import java.util.Vector;
@@ -190,7 +190,7 @@ public class SelectChannelFragment extends DialogFragment {
final SubscriptionEntity entry = subscriptions.get(position);
holder.titleView.setText(entry.getName());
holder.view.setOnClickListener(view -> clickedItem(position));
- PicassoHelper.loadAvatar(entry.getAvatarUrl()).into(holder.thumbnailView);
+ CoilHelper.INSTANCE.loadAvatar(holder.thumbnailView, entry.getAvatarUrl());
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
index 880cbb282..ea475cb4f 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SelectPlaylistFragment.java
@@ -27,7 +27,7 @@ import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
-import org.schabi.newpipe.util.image.PicassoHelper;
+import org.schabi.newpipe.util.image.CoilHelper;
import java.util.List;
import java.util.Vector;
@@ -154,21 +154,17 @@ public class SelectPlaylistFragment extends DialogFragment {
final int position) {
final PlaylistLocalItem selectedItem = playlists.get(position);
- if (selectedItem instanceof PlaylistMetadataEntry) {
- final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
-
+ if (selectedItem instanceof PlaylistMetadataEntry entry) {
holder.titleView.setText(entry.getOrderingName());
holder.view.setOnClickListener(view -> clickedItem(position));
- PicassoHelper.loadPlaylistThumbnail(entry.getThumbnailUrl())
- .into(holder.thumbnailView);
-
- } else if (selectedItem instanceof PlaylistRemoteEntity) {
- final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
+ CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView,
+ entry.getThumbnailUrl());
+ } else if (selectedItem instanceof PlaylistRemoteEntity entry) {
holder.titleView.setText(entry.getOrderingName());
holder.view.setOnClickListener(view -> clickedItem(position));
- PicassoHelper.loadPlaylistThumbnail(entry.getThumbnailUrl())
- .into(holder.thumbnailView);
+ CoilHelper.INSTANCE.loadPlaylistThumbnail(holder.thumbnailView,
+ entry.getThumbnailUrl());
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
index 9fe351b4b..d56362105 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.util.external_communication;
import static org.schabi.newpipe.MainActivity.DEBUG;
+import static coil3.Image_androidKt.toBitmap;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
@@ -9,6 +10,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
@@ -25,12 +27,15 @@ import org.schabi.newpipe.R;
import org.schabi.newpipe.RouterActivity;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.util.image.ImageStrategy;
-import org.schabi.newpipe.util.image.PicassoHelper;
-import java.io.File;
-import java.io.FileOutputStream;
+import java.nio.file.Files;
+import java.util.Collections;
import java.util.List;
+import coil3.SingletonImageLoader;
+import coil3.disk.DiskCache;
+import coil3.memory.MemoryCache;
+
public final class ShareUtils {
private static final String TAG = ShareUtils.class.getSimpleName();
@@ -273,7 +278,7 @@ public final class ShareUtils {
* @param content the content to share
* @param images a set of possible {@link Image}s of the subject, among which to choose with
* {@link ImageStrategy#choosePreferredImage(List)} since that's likely to
- * provide an image that is in Picasso's cache
+ * provide an image that is in Coil's cache
*/
public static void shareText(@NonNull final Context context,
@NonNull final String title,
@@ -334,11 +339,9 @@ public final class ShareUtils {
*
*
* In order not to worry about network issues (timeouts, DNS issues, low connection speed, ...)
- * when sharing a content, only images in the {@link com.squareup.picasso.LruCache LruCache}
- * used by the Picasso library inside {@link PicassoHelper} are used as preview images. If the
- * thumbnail image is not in the cache, no {@link ClipData} will be generated and {@code null}
- * will be returned.
- *
+ * when sharing a content, only images in the {@link MemoryCache} or {@link DiskCache}
+ * used by the Coil library are used as preview images. If the thumbnail image is not in the
+ * cache, no {@link ClipData} will be generated and {@code null} will be returned.
*
*
* In order to display the image in the content preview of the Android share sheet, an URI of
@@ -354,12 +357,6 @@ public final class ShareUtils {
*
*
*
- * This method will call {@link PicassoHelper#getImageFromCacheIfPresent(String)} to get the
- * thumbnail of the content in the {@link com.squareup.picasso.LruCache LruCache} used by
- * the Picasso library inside {@link PicassoHelper}.
- *
- *
- *
* Using the result of this method when sharing has only an effect on the system share sheet (if
* OEMs didn't change Android system standard behavior) on Android API 29 and higher.
*
@@ -373,33 +370,46 @@ public final class ShareUtils {
@NonNull final Context context,
@NonNull final String thumbnailUrl) {
try {
- final Bitmap bitmap = PicassoHelper.getImageFromCacheIfPresent(thumbnailUrl);
- if (bitmap == null) {
- return null;
- }
-
// Save the image in memory to the application's cache because we need a URI to the
// image to generate a ClipData which will show the share sheet, and so an image file
final Context applicationContext = context.getApplicationContext();
- final String appFolder = applicationContext.getCacheDir().getAbsolutePath();
- final File thumbnailPreviewFile = new File(appFolder
- + "/android_share_sheet_image_preview.jpg");
+ final var loader = SingletonImageLoader.get(context);
+ final var value = loader.getMemoryCache()
+ .get(new MemoryCache.Key(thumbnailUrl, Collections.emptyMap()));
- // Any existing file will be overwritten with FileOutputStream
- final FileOutputStream fileOutputStream = new FileOutputStream(thumbnailPreviewFile);
- bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
- fileOutputStream.close();
+ final Bitmap cachedBitmap;
+ if (value != null) {
+ cachedBitmap = toBitmap(value.getImage());
+ } else {
+ try (var snapshot = loader.getDiskCache().openSnapshot(thumbnailUrl)) {
+ if (snapshot != null) {
+ cachedBitmap = BitmapFactory.decodeFile(snapshot.getData().toString());
+ } else {
+ cachedBitmap = null;
+ }
+ }
+ }
+
+ if (cachedBitmap == null) {
+ return null;
+ }
+
+ final var path = applicationContext.getCacheDir().toPath()
+ .resolve("android_share_sheet_image_preview.jpg");
+ // Any existing file will be overwritten
+ try (var outputStream = Files.newOutputStream(path)) {
+ cachedBitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
+ }
final ClipData clipData = ClipData.newUri(applicationContext.getContentResolver(), "",
- FileProvider.getUriForFile(applicationContext,
- BuildConfig.APPLICATION_ID + ".provider",
- thumbnailPreviewFile));
+ FileProvider.getUriForFile(applicationContext,
+ BuildConfig.APPLICATION_ID + ".provider",
+ path.toFile()));
if (DEBUG) {
Log.d(TAG, "ClipData successfully generated for Android share sheet: " + clipData);
}
return clipData;
-
} catch (final Exception e) {
Log.w(TAG, "Error when setting preview image for share sheet", e);
return null;
diff --git a/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt b/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt
new file mode 100644
index 000000000..bd1c57f98
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt
@@ -0,0 +1,185 @@
+package org.schabi.newpipe.util.image
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.util.Log
+import android.widget.ImageView
+import androidx.annotation.DrawableRes
+import coil3.executeBlocking
+import coil3.imageLoader
+import coil3.request.Disposable
+import coil3.request.ImageRequest
+import coil3.request.error
+import coil3.request.placeholder
+import coil3.request.target
+import coil3.request.transformations
+import coil3.size.Size
+import coil3.target.Target
+import coil3.toBitmap
+import coil3.transform.Transformation
+import kotlin.math.min
+import org.schabi.newpipe.MainActivity
+import org.schabi.newpipe.R
+import org.schabi.newpipe.extractor.Image
+import org.schabi.newpipe.ktx.scale
+
+object CoilHelper {
+ private val TAG = CoilHelper::class.java.simpleName
+
+ @JvmOverloads
+ fun loadBitmapBlocking(
+ context: Context,
+ url: String?,
+ @DrawableRes placeholderResId: Int = 0
+ ): Bitmap? = context.imageLoader
+ .executeBlocking(getImageRequest(context, url, placeholderResId).build())
+ .image
+ ?.toBitmap()
+
+ fun loadAvatar(
+ target: ImageView,
+ images: List
+ ) {
+ loadImageDefault(target, images, R.drawable.placeholder_person)
+ }
+
+ fun loadAvatar(
+ target: ImageView,
+ url: String?
+ ) {
+ loadImageDefault(target, url, R.drawable.placeholder_person)
+ }
+
+ fun loadThumbnail(
+ target: ImageView,
+ images: List
+ ) {
+ loadImageDefault(target, images, R.drawable.placeholder_thumbnail_video)
+ }
+
+ fun loadThumbnail(
+ target: ImageView,
+ url: String?
+ ) {
+ loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video)
+ }
+
+ fun loadScaledDownThumbnail(
+ context: Context,
+ images: List,
+ target: Target
+ ): Disposable {
+ val url = ImageStrategy.choosePreferredImage(images)
+ val request =
+ getImageRequest(context, url, R.drawable.placeholder_thumbnail_video)
+ .target(target)
+ .transformations(
+ object : Transformation() {
+ override val cacheKey = "COIL_PLAYER_THUMBNAIL_TRANSFORMATION_KEY"
+
+ override suspend fun transform(
+ input: Bitmap,
+ size: Size
+ ): Bitmap {
+ if (MainActivity.DEBUG) {
+ Log.d(TAG, "Thumbnail - transform() called")
+ }
+
+ val notificationThumbnailWidth =
+ min(
+ context.resources.getDimension(R.dimen.player_notification_thumbnail_width),
+ input.width.toFloat()
+ ).toInt()
+
+ var newHeight = input.height / (input.width / notificationThumbnailWidth)
+ val result = input.scale(notificationThumbnailWidth, newHeight)
+
+ return if (result == input || !result.isMutable) {
+ // create a new mutable bitmap to prevent strange crashes on some
+ // devices (see #4638)
+ newHeight = input.height / (input.width / (notificationThumbnailWidth - 1))
+ input.scale(notificationThumbnailWidth, newHeight)
+ } else {
+ result
+ }
+ }
+ }
+ ).build()
+
+ return context.imageLoader.enqueue(request)
+ }
+
+ fun loadDetailsThumbnail(
+ target: ImageView,
+ images: List
+ ) {
+ val url = ImageStrategy.choosePreferredImage(images)
+ loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video, false)
+ }
+
+ fun loadBanner(
+ target: ImageView,
+ images: List
+ ) {
+ loadImageDefault(target, images, R.drawable.placeholder_channel_banner)
+ }
+
+ fun loadPlaylistThumbnail(
+ target: ImageView,
+ images: List
+ ) {
+ loadImageDefault(target, images, R.drawable.placeholder_thumbnail_playlist)
+ }
+
+ fun loadPlaylistThumbnail(
+ target: ImageView,
+ url: String?
+ ) {
+ loadImageDefault(target, url, R.drawable.placeholder_thumbnail_playlist)
+ }
+
+ private fun loadImageDefault(
+ target: ImageView,
+ images: List,
+ @DrawableRes placeholderResId: Int
+ ) {
+ loadImageDefault(target, ImageStrategy.choosePreferredImage(images), placeholderResId)
+ }
+
+ private fun loadImageDefault(
+ target: ImageView,
+ url: String?,
+ @DrawableRes placeholderResId: Int,
+ showPlaceholder: Boolean = true
+ ) {
+ val request =
+ getImageRequest(target.context, url, placeholderResId, showPlaceholder)
+ .target(target)
+ .build()
+ target.context.imageLoader.enqueue(request)
+ }
+
+ private fun getImageRequest(
+ context: Context,
+ url: String?,
+ @DrawableRes placeholderResId: Int,
+ showPlaceholderWhileLoading: Boolean = true
+ ): ImageRequest.Builder {
+ // if the URL was chosen with `choosePreferredImage` it will be null, but check again
+ // `shouldLoadImages` in case the URL was chosen with `imageListToDbUrl` (which is the case
+ // for URLs stored in the database)
+ val takenUrl = url?.takeIf { it.isNotEmpty() && ImageStrategy.shouldLoadImages() }
+
+ return ImageRequest
+ .Builder(context)
+ .data(takenUrl)
+ .error(placeholderResId)
+ .memoryCacheKey(takenUrl)
+ .diskCacheKey(takenUrl)
+ .apply {
+ if (takenUrl != null || showPlaceholderWhileLoading) {
+ placeholder(placeholderResId)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java
deleted file mode 100644
index 4b116bdf9..000000000
--- a/app/src/main/java/org/schabi/newpipe/util/image/PicassoHelper.java
+++ /dev/null
@@ -1,224 +0,0 @@
-package org.schabi.newpipe.util.image;
-
-import static org.schabi.newpipe.MainActivity.DEBUG;
-import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
-import static org.schabi.newpipe.util.image.ImageStrategy.choosePreferredImage;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.util.Log;
-
-import androidx.annotation.DrawableRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.graphics.BitmapCompat;
-
-import com.squareup.picasso.Cache;
-import com.squareup.picasso.LruCache;
-import com.squareup.picasso.OkHttp3Downloader;
-import com.squareup.picasso.Picasso;
-import com.squareup.picasso.RequestCreator;
-import com.squareup.picasso.Transformation;
-
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.Image;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import okhttp3.OkHttpClient;
-
-public final class PicassoHelper {
- private static final String TAG = PicassoHelper.class.getSimpleName();
- private static final String PLAYER_THUMBNAIL_TRANSFORMATION_KEY =
- "PICASSO_PLAYER_THUMBNAIL_TRANSFORMATION_KEY";
-
- private PicassoHelper() {
- }
-
- private static Cache picassoCache;
- private static OkHttpClient picassoDownloaderClient;
-
- // suppress because terminate() is called in App.onTerminate(), preventing leaks
- @SuppressLint("StaticFieldLeak")
- private static Picasso picassoInstance;
-
-
- public static void init(final Context context) {
- picassoCache = new LruCache(10 * 1024 * 1024);
- picassoDownloaderClient = new OkHttpClient.Builder()
- .cache(new okhttp3.Cache(new File(context.getExternalCacheDir(), "picasso"),
- 50L * 1024L * 1024L))
- // this should already be the default timeout in OkHttp3, but just to be sure...
- .callTimeout(15, TimeUnit.SECONDS)
- .build();
-
- picassoInstance = new Picasso.Builder(context)
- .memoryCache(picassoCache) // memory cache
- .downloader(new OkHttp3Downloader(picassoDownloaderClient)) // disk cache
- .defaultBitmapConfig(Bitmap.Config.RGB_565)
- .build();
- }
-
- public static void terminate() {
- picassoCache = null;
- picassoDownloaderClient = null;
-
- if (picassoInstance != null) {
- picassoInstance.shutdown();
- picassoInstance = null;
- }
- }
-
- public static void clearCache(final Context context) throws IOException {
- picassoInstance.shutdown();
- picassoCache.clear(); // clear memory cache
- final okhttp3.Cache diskCache = picassoDownloaderClient.cache();
- if (diskCache != null) {
- diskCache.delete(); // clear disk cache
- }
- init(context);
- }
-
- public static void cancelTag(final Object tag) {
- picassoInstance.cancelTag(tag);
- }
-
- public static void setIndicatorsEnabled(final boolean enabled) {
- picassoInstance.setIndicatorsEnabled(enabled); // useful for debugging
- }
-
-
- public static RequestCreator loadAvatar(@NonNull final List images) {
- return loadImageDefault(images, R.drawable.placeholder_person);
- }
-
- public static RequestCreator loadAvatar(@Nullable final String url) {
- return loadImageDefault(url, R.drawable.placeholder_person);
- }
-
- public static RequestCreator loadThumbnail(@NonNull final List images) {
- return loadImageDefault(images, R.drawable.placeholder_thumbnail_video);
- }
-
- public static RequestCreator loadThumbnail(@Nullable final String url) {
- return loadImageDefault(url, R.drawable.placeholder_thumbnail_video);
- }
-
- public static RequestCreator loadDetailsThumbnail(@NonNull final List images) {
- return loadImageDefault(choosePreferredImage(images),
- R.drawable.placeholder_thumbnail_video, false);
- }
-
- public static RequestCreator loadBanner(@NonNull final List images) {
- return loadImageDefault(images, R.drawable.placeholder_channel_banner);
- }
-
- public static RequestCreator loadPlaylistThumbnail(@NonNull final List images) {
- return loadImageDefault(images, R.drawable.placeholder_thumbnail_playlist);
- }
-
- public static RequestCreator loadPlaylistThumbnail(@Nullable final String url) {
- return loadImageDefault(url, R.drawable.placeholder_thumbnail_playlist);
- }
-
- public static RequestCreator loadSeekbarThumbnailPreview(@Nullable final String url) {
- return picassoInstance.load(url);
- }
-
- public static RequestCreator loadNotificationIcon(@Nullable final String url) {
- return loadImageDefault(url, R.drawable.ic_newpipe_triangle_white);
- }
-
-
- public static RequestCreator loadScaledDownThumbnail(final Context context,
- @NonNull final List images) {
- // scale down the notification thumbnail for performance
- return PicassoHelper.loadThumbnail(images)
- .transform(new Transformation() {
- @Override
- public Bitmap transform(final Bitmap source) {
- if (DEBUG) {
- Log.d(TAG, "Thumbnail - transform() called");
- }
-
- final float notificationThumbnailWidth = Math.min(
- context.getResources()
- .getDimension(R.dimen.player_notification_thumbnail_width),
- source.getWidth());
-
- final Bitmap result = BitmapCompat.createScaledBitmap(
- source,
- (int) notificationThumbnailWidth,
- (int) (source.getHeight()
- / (source.getWidth() / notificationThumbnailWidth)),
- null,
- true);
-
- if (result == source || !result.isMutable()) {
- // create a new mutable bitmap to prevent strange crashes on some
- // devices (see #4638)
- final Bitmap copied = BitmapCompat.createScaledBitmap(
- source,
- (int) notificationThumbnailWidth - 1,
- (int) (source.getHeight() / (source.getWidth()
- / (notificationThumbnailWidth - 1))),
- null,
- true);
- source.recycle();
- return copied;
- } else {
- source.recycle();
- return result;
- }
- }
-
- @Override
- public String key() {
- return PLAYER_THUMBNAIL_TRANSFORMATION_KEY;
- }
- });
- }
-
- @Nullable
- public static Bitmap getImageFromCacheIfPresent(@NonNull final String imageUrl) {
- // URLs in the internal cache finish with \n so we need to add \n to image URLs
- return picassoCache.get(imageUrl + "\n");
- }
-
-
- private static RequestCreator loadImageDefault(@NonNull final List images,
- @DrawableRes final int placeholderResId) {
- return loadImageDefault(choosePreferredImage(images), placeholderResId);
- }
-
- private static RequestCreator loadImageDefault(@Nullable final String url,
- @DrawableRes final int placeholderResId) {
- return loadImageDefault(url, placeholderResId, true);
- }
-
- private static RequestCreator loadImageDefault(@Nullable final String url,
- @DrawableRes final int placeholderResId,
- final boolean showPlaceholderWhileLoading) {
- // if the URL was chosen with `choosePreferredImage` it will be null, but check again
- // `shouldLoadImages` in case the URL was chosen with `imageListToDbUrl` (which is the case
- // for URLs stored in the database)
- if (isNullOrEmpty(url) || !ImageStrategy.shouldLoadImages()) {
- return picassoInstance
- .load((String) null)
- .placeholder(placeholderResId) // show placeholder when no image should load
- .error(placeholderResId);
- } else {
- final RequestCreator requestCreator = picassoInstance
- .load(url)
- .error(placeholderResId);
- if (showPlaceholderWhileLoading) {
- requestCreator.placeholder(placeholderResId);
- }
- return requestCreator;
- }
- }
-}
diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml
index ab6e9e345..d8592b905 100644
--- a/app/src/main/res/values/settings_keys.xml
+++ b/app/src/main/res/values/settings_keys.xml
@@ -241,7 +241,6 @@
show_memory_leaks_key
allow_disposed_exceptions_key
show_original_time_ago_key
- show_image_indicators_key
show_crash_the_player_key
check_new_streams
crash_the_app_key
diff --git a/app/src/main/res/xml/debug_settings.xml b/app/src/main/res/xml/debug_settings.xml
index 84bb281f3..d97c5aa1a 100644
--- a/app/src/main/res/xml/debug_settings.xml
+++ b/app/src/main/res/xml/debug_settings.xml
@@ -34,13 +34,6 @@
app:singleLineTitle="false"
app:iconSpaceReserved="false" />
-
-
2.8 is the last version, not 2.71828!
-picasso = "2.8"
preference = "1.2.1"
prettytime = "5.0.8.Final"
recyclerview = "1.4.0"
@@ -91,6 +90,8 @@ androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
androidx-work-runtime = { module = "androidx.work:work-runtime", version.ref = "work" }
androidx-work-rxjava3 = { module = "androidx.work:work-rxjava3", version.ref = "work" }
assertj-core = { module = "org.assertj:assertj-core", version.ref = "assertj" }
+coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
+coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
evernote-statesaver-compiler = { module = "com.evernote:android-state-processor", version.ref = "statesaver" }
evernote-statesaver-core = { module = "com.evernote:android-state", version.ref = "statesaver" }
facebook-stetho-core = { module = "com.facebook.stetho:stetho", version.ref = "stetho" }
@@ -127,7 +128,6 @@ squareup-leakcanary-core = { module = "com.squareup.leakcanary:leakcanary-androi
squareup-leakcanary-plumber = { module = "com.squareup.leakcanary:plumber-android", version.ref = "leakcanary" }
squareup-leakcanary-watcher = { module = "com.squareup.leakcanary:leakcanary-object-watcher-android", version.ref = "leakcanary" }
squareup-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
-squareup-picasso = { module = "com.squareup.picasso:picasso", version.ref = "picasso" }
zacsweers-autoservice-compiler = { module = "dev.zacsweers.autoservice:auto-service-ksp", version.ref = "autoservice-zacsweers" }
[plugins]