Migrate from Picasso to Coil
Based on changes from refactor Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
parent
273b09a3e8
commit
57364109f4
@ -272,7 +272,8 @@ dependencies {
|
||||
implementation(libs.lisawray.groupie.viewbinding)
|
||||
|
||||
// Image loading
|
||||
implementation(libs.squareup.picasso)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.coil.network.okhttp)
|
||||
|
||||
// Markdown library for Android
|
||||
implementation(libs.noties.markwon.core)
|
||||
|
||||
@ -1,11 +1,19 @@
|
||||
package org.schabi.newpipe
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.preference.PreferenceManager
|
||||
import coil3.ImageLoader
|
||||
import coil3.SingletonImageLoader
|
||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.crossfade
|
||||
import coil3.util.DebugLogger
|
||||
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||
import io.reactivex.rxjava3.exceptions.CompositeException
|
||||
import io.reactivex.rxjava3.exceptions.MissingBackpressureException
|
||||
@ -29,9 +37,8 @@ import org.schabi.newpipe.util.BridgeStateSaverInitializer
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.ServiceHelper
|
||||
import org.schabi.newpipe.util.StateSaver
|
||||
import org.schabi.newpipe.util.image.ImageStrategy.setPreferredImageQuality
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.PreferredImageQuality.Companion.fromPreferenceKey
|
||||
import org.schabi.newpipe.util.image.ImageStrategy
|
||||
import org.schabi.newpipe.util.image.PreferredImageQuality
|
||||
import org.schabi.newpipe.util.potoken.PoTokenProviderImpl
|
||||
|
||||
/*
|
||||
@ -51,7 +58,9 @@ import org.schabi.newpipe.util.potoken.PoTokenProviderImpl
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
open class App : Application() {
|
||||
open class App :
|
||||
Application(),
|
||||
SingletonImageLoader.Factory {
|
||||
var isFirstRun = false
|
||||
private set
|
||||
var notificationsRequested = false
|
||||
@ -102,9 +111,8 @@ open class App : Application() {
|
||||
|
||||
// Initialize image loader
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
PicassoHelper.init(this)
|
||||
setPreferredImageQuality(
|
||||
fromPreferenceKey(
|
||||
ImageStrategy.setPreferredImageQuality(
|
||||
PreferredImageQuality.fromPreferenceKey(
|
||||
this,
|
||||
prefs.getString(
|
||||
getString(R.string.image_quality_key),
|
||||
@ -112,20 +120,20 @@ open class App : Application() {
|
||||
)
|
||||
)
|
||||
)
|
||||
PicassoHelper.setIndicatorsEnabled(
|
||||
MainActivity.DEBUG &&
|
||||
prefs.getBoolean(getString(R.string.show_image_indicators_key), false)
|
||||
)
|
||||
|
||||
configureRxJavaErrorHandler()
|
||||
|
||||
YoutubeStreamExtractor.setPoTokenProvider(PoTokenProviderImpl)
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
super.onTerminate()
|
||||
PicassoHelper.terminate()
|
||||
}
|
||||
override fun newImageLoader(context: Context): ImageLoader = ImageLoader
|
||||
.Builder(this)
|
||||
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
|
||||
.allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
|
||||
.crossfade(true)
|
||||
.components {
|
||||
add(OkHttpNetworkFetcherFactory(callFactory = DownloaderImpl.getInstance().client))
|
||||
}.build()
|
||||
|
||||
protected open fun getDownloader(): Downloader {
|
||||
val downloader = DownloaderImpl.init(null)
|
||||
|
||||
@ -48,6 +48,11 @@ public final class DownloaderImpl extends Downloader {
|
||||
this.mCookies = new HashMap<>();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public OkHttpClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* It's recommended to call exactly once in the entire lifetime of the application.
|
||||
*
|
||||
|
||||
@ -207,10 +207,10 @@ class AboutActivity : AppCompatActivity() {
|
||||
StandardLicenses.APACHE2
|
||||
),
|
||||
SoftwareComponent(
|
||||
"Picasso",
|
||||
"2013",
|
||||
"Square, Inc.",
|
||||
"https://square.github.io/picasso/",
|
||||
"Coil",
|
||||
"2023",
|
||||
"Coil Contributors",
|
||||
"https://coil-kt.github.io/coil/",
|
||||
StandardLicenses.APACHE2
|
||||
),
|
||||
SoftwareComponent(
|
||||
|
||||
@ -118,7 +118,7 @@ import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.util.external_communication.KoreUtils;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@ -129,6 +129,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import coil3.util.CoilUtils;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjava3.disposables.Disposable;
|
||||
@ -160,8 +161,6 @@ public final class VideoDetailFragment
|
||||
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
|
||||
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
|
||||
|
||||
private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG";
|
||||
|
||||
// tabs
|
||||
private boolean showComments;
|
||||
private boolean showRelatedItems;
|
||||
@ -1499,7 +1498,10 @@ public final class VideoDetailFragment
|
||||
}
|
||||
}
|
||||
|
||||
PicassoHelper.cancelTag(PICASSO_VIDEO_DETAILS_TAG);
|
||||
CoilUtils.dispose(binding.detailThumbnailImageView);
|
||||
CoilUtils.dispose(binding.detailSubChannelThumbnailView);
|
||||
CoilUtils.dispose(binding.overlayThumbnail);
|
||||
CoilUtils.dispose(binding.detailUploaderThumbnailView);
|
||||
binding.detailThumbnailImageView.setImageBitmap(null);
|
||||
binding.detailSubChannelThumbnailView.setImageBitmap(null);
|
||||
}
|
||||
@ -1590,8 +1592,8 @@ public final class VideoDetailFragment
|
||||
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
|
||||
|
||||
checkUpdateProgressInfo(info);
|
||||
PicassoHelper.loadDetailsThumbnail(info.getThumbnails()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailThumbnailImageView);
|
||||
CoilHelper.INSTANCE.loadDetailsThumbnail(binding.detailThumbnailImageView,
|
||||
info.getThumbnails());
|
||||
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
|
||||
binding.detailMetaInfoSeparator, disposables);
|
||||
|
||||
@ -1641,8 +1643,8 @@ public final class VideoDetailFragment
|
||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailSubChannelThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView,
|
||||
info.getUploaderAvatars());
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
|
||||
}
|
||||
@ -1673,11 +1675,11 @@ public final class VideoDetailFragment
|
||||
binding.detailUploaderTextView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(info.getSubChannelAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailSubChannelThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView,
|
||||
info.getSubChannelAvatars());
|
||||
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
|
||||
PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.detailUploaderThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.detailUploaderThumbnailView,
|
||||
info.getUploaderAvatars());
|
||||
binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@ -2435,8 +2437,7 @@ public final class VideoDetailFragment
|
||||
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
|
||||
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
|
||||
binding.overlayThumbnail.setImageDrawable(null);
|
||||
PicassoHelper.loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG)
|
||||
.into(binding.overlayThumbnail);
|
||||
CoilHelper.INSTANCE.loadDetailsThumbnail(binding.overlayThumbnail, thumbnails);
|
||||
}
|
||||
|
||||
private void setOverlayPlayPauseImage(final boolean playerIsPlaying) {
|
||||
|
||||
@ -53,13 +53,14 @@ import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.StateSaver;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import coil3.util.CoilUtils;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
||||
@ -73,7 +74,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
||||
implements StateSaver.WriteRead {
|
||||
|
||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||
private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG";
|
||||
|
||||
@State
|
||||
protected int serviceId = Constants.NO_SERVICE_ID;
|
||||
@ -578,7 +578,9 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
||||
@Override
|
||||
public void showLoading() {
|
||||
super.showLoading();
|
||||
PicassoHelper.cancelTag(PICASSO_CHANNEL_TAG);
|
||||
CoilUtils.dispose(binding.channelAvatarView);
|
||||
CoilUtils.dispose(binding.channelBannerImage);
|
||||
CoilUtils.dispose(binding.subChannelAvatarView);
|
||||
animate(binding.channelSubscribeButton, false, 100);
|
||||
}
|
||||
|
||||
@ -589,17 +591,15 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
||||
setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());
|
||||
|
||||
if (ImageStrategy.shouldLoadImages() && !result.getBanners().isEmpty()) {
|
||||
PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG)
|
||||
.into(binding.channelBannerImage);
|
||||
CoilHelper.INSTANCE.loadBanner(binding.channelBannerImage, result.getBanners());
|
||||
} else {
|
||||
// do not waste space for the banner, if the user disabled images or there is not one
|
||||
binding.channelBannerImage.setImageDrawable(null);
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG)
|
||||
.into(binding.channelAvatarView);
|
||||
PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG)
|
||||
.into(binding.subChannelAvatarView);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.channelAvatarView, result.getAvatars());
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.subChannelAvatarView,
|
||||
result.getParentChannelAvatars());
|
||||
|
||||
binding.channelTitleView.setText(result.getName());
|
||||
binding.channelSubscriberView.setVisibility(View.VISIBLE);
|
||||
|
||||
@ -25,8 +25,8 @@ import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.ExtractorHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.text.TextLinkifier;
|
||||
import org.schabi.newpipe.util.text.LongPressLinkMovementMethod;
|
||||
|
||||
@ -84,7 +84,7 @@ public final class CommentRepliesFragment
|
||||
final CommentsInfoItem item = commentsInfoItem;
|
||||
|
||||
// load the author avatar
|
||||
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(binding.authorAvatar);
|
||||
CoilHelper.INSTANCE.loadAvatar(binding.authorAvatar, item.getUploaderAvatars());
|
||||
binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages()
|
||||
? View.VISIBLE : View.GONE);
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.PlayButtonHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.util.text.TextEllipsizer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -62,6 +62,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import coil3.util.CoilUtils;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Flowable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
@ -71,8 +72,6 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
||||
public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, PlaylistInfo>
|
||||
implements PlaylistControlViewHolder {
|
||||
|
||||
private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG";
|
||||
|
||||
private CompositeDisposable disposables;
|
||||
private Subscription bookmarkReactor;
|
||||
private AtomicBoolean isBookmarkButtonReady;
|
||||
@ -271,7 +270,7 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
|
||||
animate(headerBinding.getRoot(), false, 200);
|
||||
animateHideRecyclerViewAllowingScrolling(itemsList);
|
||||
|
||||
PicassoHelper.cancelTag(PICASSO_PLAYLIST_TAG);
|
||||
CoilUtils.dispose(headerBinding.uploaderAvatarView);
|
||||
animate(headerBinding.uploaderLayout, false, 200);
|
||||
}
|
||||
|
||||
@ -322,8 +321,8 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
|
||||
R.drawable.ic_radio)
|
||||
);
|
||||
} else {
|
||||
PicassoHelper.loadAvatar(result.getUploaderAvatars()).tag(PICASSO_PLAYLIST_TAG)
|
||||
.into(headerBinding.uploaderAvatarView);
|
||||
CoilHelper.INSTANCE.loadAvatar(headerBinding.uploaderAvatarView,
|
||||
result.getUploaderAvatars());
|
||||
}
|
||||
|
||||
streamCount = result.getStreamCount();
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
package org.schabi.newpipe.info_list
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import com.xwray.groupie.GroupieViewHolder
|
||||
import com.xwray.groupie.Item
|
||||
import com.xwray.groupie.viewbinding.BindableItem
|
||||
import com.xwray.groupie.viewbinding.GroupieViewHolder
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.databinding.ItemStreamSegmentBinding
|
||||
import org.schabi.newpipe.extractor.stream.StreamSegment
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.CoilHelper
|
||||
|
||||
class StreamSegmentItem(
|
||||
private val item: StreamSegment,
|
||||
private val onClick: StreamSegmentAdapter.StreamSegmentListener
|
||||
) : Item<GroupieViewHolder>() {
|
||||
) : BindableItem<ItemStreamSegmentBinding>() {
|
||||
|
||||
companion object {
|
||||
const val PAYLOAD_SELECT = 1
|
||||
@ -21,34 +20,35 @@ class StreamSegmentItem(
|
||||
|
||||
var isSelected = false
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||
item.previewUrl?.let {
|
||||
PicassoHelper.loadThumbnail(it)
|
||||
.into(viewHolder.root.findViewById<ImageView>(R.id.previewImage))
|
||||
}
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).text = item.title
|
||||
override fun bind(viewBinding: ItemStreamSegmentBinding, position: Int) {
|
||||
CoilHelper.loadThumbnail(viewBinding.previewImage, item.previewUrl)
|
||||
viewBinding.textViewTitle.text = item.title
|
||||
if (item.channelName == null) {
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.GONE
|
||||
viewBinding.textViewChannel.visibility = View.GONE
|
||||
// When the channel name is displayed there is less space
|
||||
// and thus the segment title needs to be only one line height.
|
||||
// But when there is no channel name displayed, the title can be two lines long.
|
||||
// The default maxLines value is set to 1 to display all elements in the AS preview,
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).maxLines = 2
|
||||
viewBinding.textViewTitle.maxLines = 2
|
||||
} else {
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).text = item.channelName
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.VISIBLE
|
||||
viewBinding.textViewChannel.text = item.channelName
|
||||
viewBinding.textViewChannel.visibility = View.VISIBLE
|
||||
}
|
||||
viewHolder.root.findViewById<TextView>(R.id.textViewStartSeconds).text =
|
||||
viewBinding.textViewStartSeconds.text =
|
||||
Localization.getDurationString(item.startTimeSeconds.toLong())
|
||||
viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
|
||||
viewHolder.root.setOnLongClickListener {
|
||||
viewBinding.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
|
||||
viewBinding.root.setOnLongClickListener {
|
||||
onClick.onItemLongClick(this, item.startTimeSeconds)
|
||||
true
|
||||
}
|
||||
viewHolder.root.isSelected = isSelected
|
||||
viewBinding.root.isSelected = isSelected
|
||||
}
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
override fun bind(
|
||||
viewHolder: GroupieViewHolder<ItemStreamSegmentBinding>,
|
||||
position: Int,
|
||||
payloads: MutableList<Any>
|
||||
) {
|
||||
if (payloads.contains(PAYLOAD_SELECT)) {
|
||||
viewHolder.root.isSelected = isSelected
|
||||
return
|
||||
@ -57,4 +57,6 @@ class StreamSegmentItem(
|
||||
}
|
||||
|
||||
override fun getLayout() = R.layout.item_stream_segment
|
||||
|
||||
override fun initializeViewBinding(view: View) = ItemStreamSegmentBinding.bind(view)
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ import org.schabi.newpipe.extractor.channel.ChannelInfoItem;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||
private final ImageView itemThumbnailView;
|
||||
@ -56,7 +56,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder {
|
||||
itemAdditionalDetailView.setText(getDetailLine(item));
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(item.getThumbnails()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getThumbnails());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnChannelSelectedListener() != null) {
|
||||
|
||||
@ -27,8 +27,8 @@ import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.util.image.ImageStrategy;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.text.TextEllipsizer;
|
||||
|
||||
public class CommentInfoItemHolder extends InfoItemHolder {
|
||||
@ -82,14 +82,12 @@ public class CommentInfoItemHolder extends InfoItemHolder {
|
||||
@Override
|
||||
public void updateFromItem(final InfoItem infoItem,
|
||||
final HistoryRecordManager historyRecordManager) {
|
||||
if (!(infoItem instanceof CommentsInfoItem)) {
|
||||
if (!(infoItem instanceof CommentsInfoItem item)) {
|
||||
return;
|
||||
}
|
||||
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
||||
|
||||
|
||||
// load the author avatar
|
||||
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadAvatar(itemThumbnailView, item.getUploaderAvatars());
|
||||
if (ImageStrategy.shouldLoadImages()) {
|
||||
itemThumbnailView.setVisibility(View.VISIBLE);
|
||||
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
|
||||
|
||||
@ -9,8 +9,8 @@ import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||
public final ImageView itemThumbnailView;
|
||||
@ -46,7 +46,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder {
|
||||
.localizeStreamCountMini(itemStreamCountView.getContext(), item.getStreamCount()));
|
||||
itemUploaderView.setText(item.getUploaderName());
|
||||
|
||||
PicassoHelper.loadPlaylistThumbnail(item.getThumbnails()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnails());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnPlaylistSelectedListener() != null) {
|
||||
|
||||
@ -16,8 +16,8 @@ import org.schabi.newpipe.ktx.ViewUtils;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -87,7 +87,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder {
|
||||
}
|
||||
|
||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||
PicassoHelper.loadThumbnail(item.getThumbnails()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView, item.getThumbnails());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnStreamSelectedListener() != null) {
|
||||
|
||||
13
app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt
Normal file
13
app/src/main/java/org/schabi/newpipe/ktx/Bitmap.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package org.schabi.newpipe.ktx
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
import androidx.core.graphics.BitmapCompat
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun Bitmap.scale(
|
||||
width: Int,
|
||||
height: Int,
|
||||
srcRect: Rect? = null,
|
||||
scaleInLinearSpace: Boolean = true
|
||||
) = BitmapCompat.createScaledBitmap(this, width, height, srcRect, scaleInLinearSpace)
|
||||
@ -21,7 +21,7 @@ import org.schabi.newpipe.extractor.stream.StreamType.POST_LIVE_STREAM
|
||||
import org.schabi.newpipe.extractor.stream.StreamType.VIDEO_STREAM
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.StreamTypeUtil
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.CoilHelper
|
||||
|
||||
data class StreamItem(
|
||||
val streamWithState: StreamWithState,
|
||||
@ -101,7 +101,7 @@ data class StreamItem(
|
||||
viewBinding.itemProgressView.visibility = View.GONE
|
||||
}
|
||||
|
||||
PicassoHelper.loadThumbnail(stream.thumbnailUrl).into(viewBinding.itemThumbnailView)
|
||||
CoilHelper.loadThumbnail(viewBinding.itemThumbnailView, stream.thumbnailUrl)
|
||||
|
||||
if (itemVersion != ItemVersion.MINI) {
|
||||
viewBinding.itemAdditionalDetails.text =
|
||||
|
||||
@ -6,7 +6,6 @@ import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
@ -17,20 +16,17 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.Target
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||
import org.schabi.newpipe.local.feed.service.FeedUpdateInfo
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.CoilHelper
|
||||
|
||||
/**
|
||||
* Helper for everything related to show notifications about new streams to the user.
|
||||
*/
|
||||
class NotificationHelper(val context: Context) {
|
||||
private val manager = NotificationManagerCompat.from(context)
|
||||
private val iconLoadingTargets = ArrayList<Target>()
|
||||
|
||||
/**
|
||||
* Show notifications for new streams from a single channel. The individual notifications are
|
||||
@ -71,67 +67,34 @@ class NotificationHelper(val context: Context) {
|
||||
summaryBuilder.setStyle(style)
|
||||
|
||||
// open the channel page when clicking on the summary notification
|
||||
summaryBuilder.setContentIntent(
|
||||
PendingIntentCompat.getActivity(
|
||||
context,
|
||||
data.pseudoId,
|
||||
NavigationHelper
|
||||
val intent = NavigationHelper
|
||||
.getChannelIntent(context, data.serviceId, data.url)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
|
||||
0,
|
||||
false
|
||||
)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
summaryBuilder.setContentIntent(
|
||||
PendingIntentCompat.getActivity(context, data.pseudoId, intent, 0, false)
|
||||
)
|
||||
|
||||
// a Target is like a listener for image loading events
|
||||
val target = object : Target {
|
||||
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
|
||||
// set channel icon only if there is actually one (for Android versions < 7.0)
|
||||
summaryBuilder.setLargeIcon(bitmap)
|
||||
val avatarIcon =
|
||||
CoilHelper.loadBitmapBlocking(context, data.avatarUrl, R.drawable.ic_newpipe_triangle_white)
|
||||
summaryBuilder.setLargeIcon(avatarIcon)
|
||||
|
||||
// Show individual stream notifications, set channel icon only if there is actually
|
||||
// one
|
||||
showStreamNotifications(newStreams, data.serviceId, data.url, bitmap)
|
||||
// Show summary notification if enabled
|
||||
// Show individual stream notifications, set channel icon only if there is actually one
|
||||
showStreamNotifications(newStreams, data.serviceId, avatarIcon)
|
||||
// Show summary notification
|
||||
if (manager.areNotificationsEnabled()) {
|
||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||
}
|
||||
|
||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
||||
}
|
||||
|
||||
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
|
||||
// Show individual stream notifications
|
||||
showStreamNotifications(newStreams, data.serviceId, data.url, null)
|
||||
// Show summary notification if enabled
|
||||
if (manager.areNotificationsEnabled()) {
|
||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||
}
|
||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
||||
}
|
||||
|
||||
override fun onPrepareLoad(placeHolderDrawable: Drawable) {
|
||||
// Nothing to do
|
||||
}
|
||||
}
|
||||
|
||||
// add the target to the list to hold a strong reference and prevent it from being garbage
|
||||
// collected, since Picasso only holds weak references to targets
|
||||
iconLoadingTargets.add(target)
|
||||
|
||||
PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target)
|
||||
}
|
||||
|
||||
private fun showStreamNotifications(
|
||||
newStreams: List<StreamInfoItem>,
|
||||
serviceId: Int,
|
||||
channelUrl: String,
|
||||
channelIcon: Bitmap?
|
||||
) {
|
||||
if (manager.areNotificationsEnabled()) {
|
||||
newStreams.forEach { stream ->
|
||||
val notification =
|
||||
createStreamNotification(stream, serviceId, channelUrl, channelIcon)
|
||||
createStreamNotification(stream, serviceId, channelIcon)
|
||||
manager.notify(stream.url.hashCode(), notification)
|
||||
}
|
||||
}
|
||||
@ -140,7 +103,6 @@ class NotificationHelper(val context: Context) {
|
||||
private fun createStreamNotification(
|
||||
item: StreamInfoItem,
|
||||
serviceId: Int,
|
||||
channelUrl: String,
|
||||
channelIcon: Bitmap?
|
||||
): Notification {
|
||||
return NotificationCompat.Builder(
|
||||
@ -151,7 +113,7 @@ class NotificationHelper(val context: Context) {
|
||||
.setLargeIcon(channelIcon)
|
||||
.setContentTitle(item.name)
|
||||
.setContentText(item.uploaderName)
|
||||
.setGroup(channelUrl)
|
||||
.setGroup(item.uploaderUrl)
|
||||
.setColor(ContextCompat.getColor(context, R.color.ic_launcher_background))
|
||||
.setColorized(true)
|
||||
.setAutoCancel(true)
|
||||
|
||||
@ -8,8 +8,8 @@ import org.schabi.newpipe.database.playlist.PlaylistDuplicatesEntry;
|
||||
import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.Localization;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@ -30,17 +30,16 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder {
|
||||
public void updateFromItem(final LocalItem localItem,
|
||||
final HistoryRecordManager historyRecordManager,
|
||||
final DateTimeFormatter dateTimeFormatter) {
|
||||
if (!(localItem instanceof PlaylistMetadataEntry)) {
|
||||
if (!(localItem instanceof PlaylistMetadataEntry item)) {
|
||||
return;
|
||||
}
|
||||
final PlaylistMetadataEntry item = (PlaylistMetadataEntry) localItem;
|
||||
|
||||
itemTitleView.setText(item.getOrderingName());
|
||||
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
||||
itemStreamCountView.getContext(), item.getStreamCount()));
|
||||
itemUploaderView.setVisibility(View.INVISIBLE);
|
||||
|
||||
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnailUrl());
|
||||
|
||||
if (item instanceof PlaylistDuplicatesEntry
|
||||
&& ((PlaylistDuplicatesEntry) item).getTimesStreamIsContained() > 0) {
|
||||
|
||||
@ -16,8 +16,8 @@ import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||
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;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@ -83,8 +83,8 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder {
|
||||
}
|
||||
|
||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||
PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl())
|
||||
.into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView,
|
||||
item.getStreamEntity().getThumbnailUrl());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
|
||||
@ -16,8 +16,8 @@ import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.util.DependentPreferenceHelper;
|
||||
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;
|
||||
import org.schabi.newpipe.views.AnimatedProgressBar;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@ -117,8 +117,8 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder {
|
||||
}
|
||||
|
||||
// Default thumbnail is shown on error, while loading and if the url is empty
|
||||
PicassoHelper.loadThumbnail(item.getStreamEntity().getThumbnailUrl())
|
||||
.into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadThumbnail(itemThumbnailView,
|
||||
item.getStreamEntity().getThumbnailUrl());
|
||||
|
||||
itemView.setOnClickListener(view -> {
|
||||
if (itemBuilder.getOnItemSelectedListener() != null) {
|
||||
|
||||
@ -8,8 +8,8 @@ import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity;
|
||||
import org.schabi.newpipe.local.LocalItemBuilder;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
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;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@ -29,10 +29,9 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||
public void updateFromItem(final LocalItem localItem,
|
||||
final HistoryRecordManager historyRecordManager,
|
||||
final DateTimeFormatter dateTimeFormatter) {
|
||||
if (!(localItem instanceof PlaylistRemoteEntity)) {
|
||||
if (!(localItem instanceof PlaylistRemoteEntity item)) {
|
||||
return;
|
||||
}
|
||||
final PlaylistRemoteEntity item = (PlaylistRemoteEntity) localItem;
|
||||
|
||||
itemTitleView.setText(item.getOrderingName());
|
||||
itemStreamCountView.setText(Localization.localizeStreamCountMini(
|
||||
@ -45,7 +44,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder {
|
||||
itemUploaderView.setText(ServiceHelper.getNameOfServiceById(item.getServiceId()));
|
||||
}
|
||||
|
||||
PicassoHelper.loadPlaylistThumbnail(item.getThumbnailUrl()).into(itemThumbnailView);
|
||||
CoilHelper.INSTANCE.loadPlaylistThumbnail(itemThumbnailView, item.getThumbnailUrl());
|
||||
|
||||
super.updateFromItem(localItem, historyRecordManager, dateTimeFormatter);
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItem
|
||||
import org.schabi.newpipe.util.Localization
|
||||
import org.schabi.newpipe.util.OnClickGesture
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.CoilHelper
|
||||
|
||||
class ChannelItem(
|
||||
private val infoItem: ChannelInfoItem,
|
||||
@ -39,7 +39,7 @@ class ChannelItem(
|
||||
itemChannelDescriptionView.text = infoItem.description
|
||||
}
|
||||
|
||||
PicassoHelper.loadAvatar(infoItem.thumbnails).into(itemThumbnailView)
|
||||
CoilHelper.loadAvatar(itemThumbnailView, infoItem.thumbnails)
|
||||
|
||||
gesturesListener?.run {
|
||||
viewHolder.root.setOnClickListener { selected(infoItem) }
|
||||
|
||||
@ -10,7 +10,7 @@ import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.databinding.PickerSubscriptionItemBinding
|
||||
import org.schabi.newpipe.ktx.AnimationType
|
||||
import org.schabi.newpipe.ktx.animate
|
||||
import org.schabi.newpipe.util.image.PicassoHelper
|
||||
import org.schabi.newpipe.util.image.CoilHelper
|
||||
|
||||
data class PickerSubscriptionItem(
|
||||
val subscriptionEntity: SubscriptionEntity,
|
||||
@ -21,7 +21,7 @@ data class PickerSubscriptionItem(
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
|
||||
|
||||
override fun bind(viewBinding: PickerSubscriptionItemBinding, position: Int) {
|
||||
PicassoHelper.loadAvatar(subscriptionEntity.avatarUrl).into(viewBinding.thumbnailView)
|
||||
CoilHelper.loadAvatar(viewBinding.thumbnailView, subscriptionEntity.avatarUrl)
|
||||
viewBinding.titleView.text = subscriptionEntity.name
|
||||
viewBinding.selectedHighlight.isVisible = isSelected
|
||||
}
|
||||
|
||||
@ -46,13 +46,14 @@ import static org.schabi.newpipe.util.ListHelper.getPopupResolutionIndex;
|
||||
import static org.schabi.newpipe.util.ListHelper.getResolutionIndex;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
import static coil3.Image_androidKt.toBitmap;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.AudioManager;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.util.Log;
|
||||
@ -80,8 +81,6 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
import com.squareup.picasso.Picasso;
|
||||
import com.squareup.picasso.Target;
|
||||
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
@ -126,13 +125,14 @@ import org.schabi.newpipe.util.ListHelper;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.SerializedCache;
|
||||
import org.schabi.newpipe.util.StreamTypeUtil;
|
||||
import org.schabi.newpipe.util.image.PicassoHelper;
|
||||
import org.schabi.newpipe.util.image.CoilHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import coil3.target.Target;
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.rxjava3.core.Observable;
|
||||
import io.reactivex.rxjava3.core.Single;
|
||||
@ -181,7 +181,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static final int RENDERER_UNAVAILABLE = -1;
|
||||
private static final String PICASSO_PLAYER_THUMBNAIL_TAG = "PICASSO_PLAYER_THUMBNAIL_TAG";
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playback
|
||||
@ -200,6 +199,8 @@ public final class Player implements PlaybackListener, Listener {
|
||||
private MediaItemTag currentMetadata;
|
||||
@Nullable
|
||||
private Bitmap currentThumbnail;
|
||||
@Nullable
|
||||
private coil3.request.Disposable thumbnailDisposable;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Player
|
||||
@ -255,12 +256,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||
@NonNull
|
||||
private final CompositeDisposable streamItemDisposable = new CompositeDisposable();
|
||||
|
||||
// This is the only listener we need for thumbnail loading, since there is always at most only
|
||||
// one thumbnail being loaded at a time. This field is also here to maintain a strong reference,
|
||||
// which would otherwise be garbage collected since Picasso holds weak references to targets.
|
||||
@NonNull
|
||||
private final Target currentThumbnailTarget;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@ -314,8 +309,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||
videoResolver = new VideoPlaybackResolver(context, dataSource, getQualityResolver());
|
||||
audioResolver = new AudioPlaybackResolver(context, dataSource);
|
||||
|
||||
currentThumbnailTarget = getCurrentThumbnailTarget();
|
||||
|
||||
// The UIs added here should always be present. They will be initialized when the player
|
||||
// reaches the initialization step. Make sure the media session ui is before the
|
||||
// notification ui in the UIs list, since the notification depends on the media session in
|
||||
@ -704,7 +697,6 @@ public final class Player implements PlaybackListener, Listener {
|
||||
databaseUpdateDisposable.clear();
|
||||
progressUpdateDisposable.set(null);
|
||||
streamItemDisposable.clear();
|
||||
cancelLoadingCurrentThumbnail();
|
||||
|
||||
UIs.destroyAll(Object.class); // destroy every UI: obviously every UI extends Object
|
||||
}
|
||||
@ -884,67 +876,58 @@ public final class Player implements PlaybackListener, Listener {
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
//region Thumbnail loading
|
||||
|
||||
private Target getCurrentThumbnailTarget() {
|
||||
// a Picasso target is just a listener for thumbnail loading events
|
||||
return new Target() {
|
||||
@Override
|
||||
public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Thumbnail - onBitmapLoaded() called with: bitmap = [" + bitmap
|
||||
+ " -> " + bitmap.getWidth() + "x" + bitmap.getHeight() + "], from = ["
|
||||
+ from + "]");
|
||||
}
|
||||
// there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too.
|
||||
onThumbnailLoaded(bitmap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBitmapFailed(final Exception e, final Drawable errorDrawable) {
|
||||
Log.e(TAG, "Thumbnail - onBitmapFailed() called", e);
|
||||
// there is a new thumbnail, so e.g. the end screen thumbnail needs to change, too.
|
||||
onThumbnailLoaded(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrepareLoad(final Drawable placeHolderDrawable) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Thumbnail - onPrepareLoad() called");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void loadCurrentThumbnail(final List<Image> 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);
|
||||
}
|
||||
|
||||
private void cancelLoadingCurrentThumbnail() {
|
||||
// cancel the Picasso job associated with the player thumbnail, if any
|
||||
PicassoHelper.cancelTag(PICASSO_PLAYER_THUMBNAIL_TAG);
|
||||
@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 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));
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 "
|
||||
|
||||
@ -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());
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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 {
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
@ -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));
|
||||
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;
|
||||
|
||||
185
app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt
Normal file
185
app/src/main/java/org/schabi/newpipe/util/image/CoilHelper.kt
Normal file
@ -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<Image>
|
||||
) {
|
||||
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<Image>
|
||||
) {
|
||||
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<Image>,
|
||||
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<Image>
|
||||
) {
|
||||
val url = ImageStrategy.choosePreferredImage(images)
|
||||
loadImageDefault(target, url, R.drawable.placeholder_thumbnail_video, false)
|
||||
}
|
||||
|
||||
fun loadBanner(
|
||||
target: ImageView,
|
||||
images: List<Image>
|
||||
) {
|
||||
loadImageDefault(target, images, R.drawable.placeholder_channel_banner)
|
||||
}
|
||||
|
||||
fun loadPlaylistThumbnail(
|
||||
target: ImageView,
|
||||
images: List<Image>
|
||||
) {
|
||||
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<Image>,
|
||||
@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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Image> 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<Image> 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<Image> images) {
|
||||
return loadImageDefault(choosePreferredImage(images),
|
||||
R.drawable.placeholder_thumbnail_video, false);
|
||||
}
|
||||
|
||||
public static RequestCreator loadBanner(@NonNull final List<Image> images) {
|
||||
return loadImageDefault(images, R.drawable.placeholder_channel_banner);
|
||||
}
|
||||
|
||||
public static RequestCreator loadPlaylistThumbnail(@NonNull final List<Image> 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<Image> 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<Image> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,7 +241,6 @@
|
||||
<string name="show_memory_leaks_key">show_memory_leaks_key</string>
|
||||
<string name="allow_disposed_exceptions_key">allow_disposed_exceptions_key</string>
|
||||
<string name="show_original_time_ago_key">show_original_time_ago_key</string>
|
||||
<string name="show_image_indicators_key">show_image_indicators_key</string>
|
||||
<string name="show_crash_the_player_key">show_crash_the_player_key</string>
|
||||
<string name="check_new_streams_key">check_new_streams</string>
|
||||
<string name="crash_the_app_key">crash_the_app_key</string>
|
||||
|
||||
@ -34,13 +34,6 @@
|
||||
app:singleLineTitle="false"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="@string/show_image_indicators_key"
|
||||
android:summary="@string/show_image_indicators_summary"
|
||||
android:title="@string/show_image_indicators_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="@string/show_crash_the_player_key"
|
||||
|
||||
@ -13,6 +13,7 @@ autoservice-zacsweers = "1.2.0"
|
||||
bridge = "v2.0.2"
|
||||
cardview = "1.0.0"
|
||||
checkstyle = "13.0.0"
|
||||
coil = "3.0.4"
|
||||
constraintlayout = "2.2.1"
|
||||
core = "1.17.0"
|
||||
desugar = "2.1.5"
|
||||
@ -35,8 +36,6 @@ media = "1.7.1"
|
||||
mockitoCore = "5.21.0"
|
||||
okhttp = "5.3.2"
|
||||
phoenix = "3.0.0"
|
||||
#noinspection NewerVersionAvailable,GradleDependency --> 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]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user