Skip to content

Commit 73e3a69

Browse files
Migrate image loading from Picasso to Coil (#11201)
* Load notification icons using Coil * Migrate to Coil from Picasso * Clean up Picasso leftovers * Enable RGB-565 for low-end devices * Added Coil helper method * Add annotation * Simplify newImageLoader implementation * Use Coil's default disk and memory cache config * Enable crossfade animation * Correct method name * Fix thumbnail not being displayed in media notification
1 parent 03167a1 commit 73e3a69

File tree

92 files changed

+330
-596
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+330
-596
lines changed

app/build.gradle

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,7 @@ dependencies {
267267
implementation "com.github.lisawray.groupie:groupie-viewbinding:${groupieVersion}"
268268

269269
// Image loading
270-
//noinspection GradleDependency --> 2.8 is the last version, not 2.71828!
271-
implementation "com.squareup.picasso:picasso:2.8"
270+
implementation 'io.coil-kt:coil:2.6.0'
272271

273272
// Markdown library for Android
274273
implementation "io.noties.markwon:core:${markwonVersion}"

app/src/main/java/org/schabi/newpipe/App.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.schabi.newpipe;
22

3+
import android.app.ActivityManager;
34
import android.app.Application;
45
import android.content.Context;
56
import android.content.SharedPreferences;
@@ -8,6 +9,7 @@
89
import androidx.annotation.NonNull;
910
import androidx.core.app.NotificationChannelCompat;
1011
import androidx.core.app.NotificationManagerCompat;
12+
import androidx.core.content.ContextCompat;
1113
import androidx.preference.PreferenceManager;
1214

1315
import com.jakewharton.processphoenix.ProcessPhoenix;
@@ -20,10 +22,9 @@
2022
import org.schabi.newpipe.ktx.ExceptionUtils;
2123
import org.schabi.newpipe.settings.NewPipeSettings;
2224
import org.schabi.newpipe.util.Localization;
23-
import org.schabi.newpipe.util.image.ImageStrategy;
24-
import org.schabi.newpipe.util.image.PicassoHelper;
2525
import org.schabi.newpipe.util.ServiceHelper;
2626
import org.schabi.newpipe.util.StateSaver;
27+
import org.schabi.newpipe.util.image.ImageStrategy;
2728
import org.schabi.newpipe.util.image.PreferredImageQuality;
2829

2930
import java.io.IOException;
@@ -32,6 +33,9 @@
3233
import java.util.List;
3334
import java.util.Objects;
3435

36+
import coil.ImageLoader;
37+
import coil.ImageLoaderFactory;
38+
import coil.util.DebugLogger;
3539
import io.reactivex.rxjava3.exceptions.CompositeException;
3640
import io.reactivex.rxjava3.exceptions.MissingBackpressureException;
3741
import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
@@ -57,7 +61,7 @@
5761
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
5862
*/
5963

60-
public class App extends Application {
64+
public class App extends Application implements ImageLoaderFactory {
6165
public static final String PACKAGE_NAME = BuildConfig.APPLICATION_ID;
6266
private static final String TAG = App.class.toString();
6367

@@ -108,20 +112,22 @@ public void onCreate() {
108112

109113
// Initialize image loader
110114
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
111-
PicassoHelper.init(this);
112115
ImageStrategy.setPreferredImageQuality(PreferredImageQuality.fromPreferenceKey(this,
113116
prefs.getString(getString(R.string.image_quality_key),
114117
getString(R.string.image_quality_default))));
115-
PicassoHelper.setIndicatorsEnabled(MainActivity.DEBUG
116-
&& prefs.getBoolean(getString(R.string.show_image_indicators_key), false));
117118

118119
configureRxJavaErrorHandler();
119120
}
120121

122+
@NonNull
121123
@Override
122-
public void onTerminate() {
123-
super.onTerminate();
124-
PicassoHelper.terminate();
124+
public ImageLoader newImageLoader() {
125+
return new ImageLoader.Builder(this)
126+
.allowRgb565(ContextCompat.getSystemService(this, ActivityManager.class)
127+
.isLowRamDevice())
128+
.logger(BuildConfig.DEBUG ? new DebugLogger() : null)
129+
.crossfade(true)
130+
.build();
125131
}
126132

127133
protected Downloader getDownloader() {

app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ class AboutActivity : AppCompatActivity() {
167167
"https://square.github.io/okhttp/", StandardLicenses.APACHE2
168168
),
169169
SoftwareComponent(
170-
"Picasso", "2013", "Square, Inc.",
171-
"https://square.github.io/picasso/", StandardLicenses.APACHE2
170+
"Coil", "2023", "Coil Contributors",
171+
"https://coil-kt.github.io/coil/", StandardLicenses.APACHE2
172172
),
173173
SoftwareComponent(
174174
"PrettyTime", "2012 - 2020", "Lincoln Baxter, III",

app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@
116116
import org.schabi.newpipe.util.ThemeHelper;
117117
import org.schabi.newpipe.util.external_communication.KoreUtils;
118118
import org.schabi.newpipe.util.external_communication.ShareUtils;
119-
import org.schabi.newpipe.util.image.PicassoHelper;
119+
import org.schabi.newpipe.util.image.CoilHelper;
120120

121121
import java.util.ArrayList;
122122
import java.util.Iterator;
@@ -127,6 +127,7 @@
127127
import java.util.concurrent.TimeUnit;
128128
import java.util.function.Consumer;
129129

130+
import coil.util.CoilUtils;
130131
import icepick.State;
131132
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
132133
import io.reactivex.rxjava3.disposables.CompositeDisposable;
@@ -159,8 +160,6 @@ public final class VideoDetailFragment
159160
private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB";
160161
private static final String EMPTY_TAB_TAG = "EMPTY TAB";
161162

162-
private static final String PICASSO_VIDEO_DETAILS_TAG = "PICASSO_VIDEO_DETAILS_TAG";
163-
164163
// tabs
165164
private boolean showComments;
166165
private boolean showRelatedItems;
@@ -1471,7 +1470,10 @@ public void showLoading() {
14711470
}
14721471
}
14731472

1474-
PicassoHelper.cancelTag(PICASSO_VIDEO_DETAILS_TAG);
1473+
CoilUtils.dispose(binding.detailThumbnailImageView);
1474+
CoilUtils.dispose(binding.detailSubChannelThumbnailView);
1475+
CoilUtils.dispose(binding.overlayThumbnail);
1476+
14751477
binding.detailThumbnailImageView.setImageBitmap(null);
14761478
binding.detailSubChannelThumbnailView.setImageBitmap(null);
14771479
}
@@ -1562,8 +1564,8 @@ public void handleResult(@NonNull final StreamInfo info) {
15621564
binding.detailSecondaryControlPanel.setVisibility(View.GONE);
15631565

15641566
checkUpdateProgressInfo(info);
1565-
PicassoHelper.loadDetailsThumbnail(info.getThumbnails()).tag(PICASSO_VIDEO_DETAILS_TAG)
1566-
.into(binding.detailThumbnailImageView);
1567+
CoilHelper.INSTANCE.loadDetailsThumbnail(binding.detailThumbnailImageView,
1568+
info.getThumbnails());
15671569
showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView,
15681570
binding.detailMetaInfoSeparator, disposables);
15691571

@@ -1613,8 +1615,8 @@ private void displayUploaderAsSubChannel(final StreamInfo info) {
16131615
binding.detailUploaderTextView.setVisibility(View.GONE);
16141616
}
16151617

1616-
PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
1617-
.into(binding.detailSubChannelThumbnailView);
1618+
CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView,
1619+
info.getUploaderAvatars());
16181620
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
16191621
binding.detailUploaderThumbnailView.setVisibility(View.GONE);
16201622
}
@@ -1645,11 +1647,11 @@ private void displayBothUploaderAndSubChannel(final StreamInfo info) {
16451647
binding.detailUploaderTextView.setVisibility(View.GONE);
16461648
}
16471649

1648-
PicassoHelper.loadAvatar(info.getSubChannelAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
1649-
.into(binding.detailSubChannelThumbnailView);
1650+
CoilHelper.INSTANCE.loadAvatar(binding.detailSubChannelThumbnailView,
1651+
info.getSubChannelAvatars());
16501652
binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE);
1651-
PicassoHelper.loadAvatar(info.getUploaderAvatars()).tag(PICASSO_VIDEO_DETAILS_TAG)
1652-
.into(binding.detailUploaderThumbnailView);
1653+
CoilHelper.INSTANCE.loadAvatar(binding.detailUploaderThumbnailView,
1654+
info.getUploaderAvatars());
16531655
binding.detailUploaderThumbnailView.setVisibility(View.VISIBLE);
16541656
}
16551657

@@ -2403,8 +2405,7 @@ private void updateOverlayData(@Nullable final String overlayTitle,
24032405
binding.overlayTitleTextView.setText(isEmpty(overlayTitle) ? "" : overlayTitle);
24042406
binding.overlayChannelTextView.setText(isEmpty(uploader) ? "" : uploader);
24052407
binding.overlayThumbnail.setImageDrawable(null);
2406-
PicassoHelper.loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG)
2407-
.into(binding.overlayThumbnail);
2408+
CoilHelper.INSTANCE.loadDetailsThumbnail(binding.overlayThumbnail, thumbnails);
24082409
}
24092410

24102411
private void setOverlayPlayPauseImage(final boolean playerIsPlaying) {

app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,16 @@
5050
import org.schabi.newpipe.util.Localization;
5151
import org.schabi.newpipe.util.NavigationHelper;
5252
import org.schabi.newpipe.util.StateSaver;
53-
import org.schabi.newpipe.util.image.ImageStrategy;
54-
import org.schabi.newpipe.util.image.PicassoHelper;
5553
import org.schabi.newpipe.util.ThemeHelper;
5654
import org.schabi.newpipe.util.external_communication.ShareUtils;
55+
import org.schabi.newpipe.util.image.CoilHelper;
56+
import org.schabi.newpipe.util.image.ImageStrategy;
5757

5858
import java.util.List;
5959
import java.util.Queue;
6060
import java.util.concurrent.TimeUnit;
6161

62+
import coil.util.CoilUtils;
6263
import icepick.State;
6364
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
6465
import io.reactivex.rxjava3.core.Observable;
@@ -73,7 +74,6 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
7374
implements StateSaver.WriteRead {
7475

7576
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
76-
private static final String PICASSO_CHANNEL_TAG = "PICASSO_CHANNEL_TAG";
7777

7878
@State
7979
protected int serviceId = Constants.NO_SERVICE_ID;
@@ -576,7 +576,9 @@ private void runWorker(final boolean forceLoad) {
576576
@Override
577577
public void showLoading() {
578578
super.showLoading();
579-
PicassoHelper.cancelTag(PICASSO_CHANNEL_TAG);
579+
CoilUtils.dispose(binding.channelAvatarView);
580+
CoilUtils.dispose(binding.channelBannerImage);
581+
CoilUtils.dispose(binding.subChannelAvatarView);
580582
animate(binding.channelSubscribeButton, false, 100);
581583
}
582584

@@ -587,17 +589,15 @@ public void handleResult(@NonNull final ChannelInfo result) {
587589
setInitialData(result.getServiceId(), result.getOriginalUrl(), result.getName());
588590

589591
if (ImageStrategy.shouldLoadImages() && !result.getBanners().isEmpty()) {
590-
PicassoHelper.loadBanner(result.getBanners()).tag(PICASSO_CHANNEL_TAG)
591-
.into(binding.channelBannerImage);
592+
CoilHelper.INSTANCE.loadBanner(binding.channelBannerImage, result.getBanners());
592593
} else {
593594
// do not waste space for the banner, if the user disabled images or there is not one
594595
binding.channelBannerImage.setImageDrawable(null);
595596
}
596597

597-
PicassoHelper.loadAvatar(result.getAvatars()).tag(PICASSO_CHANNEL_TAG)
598-
.into(binding.channelAvatarView);
599-
PicassoHelper.loadAvatar(result.getParentChannelAvatars()).tag(PICASSO_CHANNEL_TAG)
600-
.into(binding.subChannelAvatarView);
598+
CoilHelper.INSTANCE.loadAvatar(binding.channelAvatarView, result.getAvatars());
599+
CoilHelper.INSTANCE.loadAvatar(binding.subChannelAvatarView,
600+
result.getParentChannelAvatars());
601601

602602
binding.channelTitleView.setText(result.getName());
603603
binding.channelSubscriberView.setVisibility(View.VISIBLE);

app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentRepliesFragment.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import org.schabi.newpipe.util.ExtractorHelper;
2424
import org.schabi.newpipe.util.Localization;
2525
import org.schabi.newpipe.util.NavigationHelper;
26+
import org.schabi.newpipe.util.image.CoilHelper;
2627
import org.schabi.newpipe.util.image.ImageStrategy;
27-
import org.schabi.newpipe.util.image.PicassoHelper;
2828
import org.schabi.newpipe.util.text.TextLinkifier;
2929

3030
import java.util.Queue;
@@ -82,7 +82,7 @@ protected Supplier<View> getListHeaderSupplier() {
8282
final CommentsInfoItem item = commentsInfoItem;
8383

8484
// load the author avatar
85-
PicassoHelper.loadAvatar(item.getUploaderAvatars()).into(binding.authorAvatar);
85+
CoilHelper.INSTANCE.loadAvatar(binding.authorAvatar, item.getUploaderAvatars());
8686
binding.authorAvatar.setVisibility(ImageStrategy.shouldLoadImages()
8787
? View.VISIBLE : View.GONE);
8888

app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
import org.schabi.newpipe.util.NavigationHelper;
5454
import org.schabi.newpipe.util.PlayButtonHelper;
5555
import org.schabi.newpipe.util.external_communication.ShareUtils;
56-
import org.schabi.newpipe.util.image.PicassoHelper;
56+
import org.schabi.newpipe.util.image.CoilHelper;
5757
import org.schabi.newpipe.util.text.TextEllipsizer;
5858

5959
import java.util.ArrayList;
@@ -62,6 +62,7 @@
6262
import java.util.function.Supplier;
6363
import java.util.stream.Collectors;
6464

65+
import coil.util.CoilUtils;
6566
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
6667
import io.reactivex.rxjava3.core.Flowable;
6768
import io.reactivex.rxjava3.core.Single;
@@ -71,8 +72,6 @@
7172
public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, PlaylistInfo>
7273
implements PlaylistControlViewHolder {
7374

74-
private static final String PICASSO_PLAYLIST_TAG = "PICASSO_PLAYLIST_TAG";
75-
7675
private CompositeDisposable disposables;
7776
private Subscription bookmarkReactor;
7877
private AtomicBoolean isBookmarkButtonReady;
@@ -276,7 +275,7 @@ public void showLoading() {
276275
animate(headerBinding.getRoot(), false, 200);
277276
animateHideRecyclerViewAllowingScrolling(itemsList);
278277

279-
PicassoHelper.cancelTag(PICASSO_PLAYLIST_TAG);
278+
CoilUtils.dispose(headerBinding.uploaderAvatarView);
280279
animate(headerBinding.uploaderLayout, false, 200);
281280
}
282281

@@ -327,8 +326,8 @@ public void handleResult(@NonNull final PlaylistInfo result) {
327326
R.drawable.ic_radio)
328327
);
329328
} else {
330-
PicassoHelper.loadAvatar(result.getUploaderAvatars()).tag(PICASSO_PLAYLIST_TAG)
331-
.into(headerBinding.uploaderAvatarView);
329+
CoilHelper.INSTANCE.loadAvatar(headerBinding.uploaderAvatarView,
330+
result.getUploaderAvatars());
332331
}
333332

334333
streamCount = result.getStreamCount();
Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,51 @@
11
package org.schabi.newpipe.info_list
22

33
import android.view.View
4-
import android.widget.ImageView
5-
import android.widget.TextView
6-
import com.xwray.groupie.GroupieViewHolder
7-
import com.xwray.groupie.Item
4+
import com.xwray.groupie.viewbinding.BindableItem
5+
import com.xwray.groupie.viewbinding.GroupieViewHolder
86
import org.schabi.newpipe.R
7+
import org.schabi.newpipe.databinding.ItemStreamSegmentBinding
98
import org.schabi.newpipe.extractor.stream.StreamSegment
109
import org.schabi.newpipe.util.Localization
11-
import org.schabi.newpipe.util.image.PicassoHelper
10+
import org.schabi.newpipe.util.image.CoilHelper
1211

1312
class StreamSegmentItem(
1413
private val item: StreamSegment,
1514
private val onClick: StreamSegmentAdapter.StreamSegmentListener
16-
) : Item<GroupieViewHolder>() {
15+
) : BindableItem<ItemStreamSegmentBinding>() {
1716

1817
companion object {
1918
const val PAYLOAD_SELECT = 1
2019
}
2120

2221
var isSelected = false
2322

24-
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
25-
item.previewUrl?.let {
26-
PicassoHelper.loadThumbnail(it)
27-
.into(viewHolder.root.findViewById<ImageView>(R.id.previewImage))
28-
}
29-
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).text = item.title
23+
override fun bind(viewBinding: ItemStreamSegmentBinding, position: Int) {
24+
CoilHelper.loadThumbnail(viewBinding.previewImage, item.previewUrl)
25+
viewBinding.textViewTitle.text = item.title
3026
if (item.channelName == null) {
31-
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.GONE
27+
viewBinding.textViewChannel.visibility = View.GONE
3228
// When the channel name is displayed there is less space
3329
// and thus the segment title needs to be only one line height.
3430
// But when there is no channel name displayed, the title can be two lines long.
3531
// The default maxLines value is set to 1 to display all elements in the AS preview,
36-
viewHolder.root.findViewById<TextView>(R.id.textViewTitle).maxLines = 2
32+
viewBinding.textViewTitle.maxLines = 2
3733
} else {
38-
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).text = item.channelName
39-
viewHolder.root.findViewById<TextView>(R.id.textViewChannel).visibility = View.VISIBLE
34+
viewBinding.textViewChannel.text = item.channelName
35+
viewBinding.textViewChannel.visibility = View.VISIBLE
4036
}
41-
viewHolder.root.findViewById<TextView>(R.id.textViewStartSeconds).text =
37+
viewBinding.textViewStartSeconds.text =
4238
Localization.getDurationString(item.startTimeSeconds.toLong())
43-
viewHolder.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
44-
viewHolder.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true }
45-
viewHolder.root.isSelected = isSelected
39+
viewBinding.root.setOnClickListener { onClick.onItemClick(this, item.startTimeSeconds) }
40+
viewBinding.root.setOnLongClickListener { onClick.onItemLongClick(this, item.startTimeSeconds); true }
41+
viewBinding.root.isSelected = isSelected
4642
}
4743

48-
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
44+
override fun bind(
45+
viewHolder: GroupieViewHolder<ItemStreamSegmentBinding>,
46+
position: Int,
47+
payloads: MutableList<Any>
48+
) {
4949
if (payloads.contains(PAYLOAD_SELECT)) {
5050
viewHolder.root.isSelected = isSelected
5151
return
@@ -54,4 +54,6 @@ class StreamSegmentItem(
5454
}
5555

5656
override fun getLayout() = R.layout.item_stream_segment
57+
58+
override fun initializeViewBinding(view: View) = ItemStreamSegmentBinding.bind(view)
5759
}

0 commit comments

Comments
 (0)