Fix loading icon in streams notifications

This commit is contained in:
Stypox 2021-12-12 20:18:16 +01:00
parent 19fd7bc37e
commit 01f3ed0e5e
No known key found for this signature in database
GPG key ID: 4BDF1B40A49FDD23
4 changed files with 67 additions and 102 deletions

View file

@ -4,7 +4,6 @@ import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.Settings import android.provider.Settings
@ -12,14 +11,11 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.service.FeedUpdateInfo import org.schabi.newpipe.local.feed.service.FeedUpdateInfo
import org.schabi.newpipe.util.NavigationHelper import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.PicassoHelper
/** /**
* Helper for everything related to show notifications about new streams to the user. * Helper for everything related to show notifications about new streams to the user.
@ -34,7 +30,7 @@ class NotificationHelper(val context: Context) {
* Show a notification about new streams from a single channel. * Show a notification about new streams from a single channel.
* Opening the notification will open the corresponding channel page. * Opening the notification will open the corresponding channel page.
*/ */
fun displayNewStreamsNotification(data: FeedUpdateInfo): Completable { fun displayNewStreamsNotification(data: FeedUpdateInfo) {
val newStreams: List<StreamInfoItem> = data.newStreams val newStreams: List<StreamInfoItem> = data.newStreams
val summary = context.resources.getQuantityString( val summary = context.resources.getQuantityString(
R.plurals.new_streams, newStreams.size, newStreams.size R.plurals.new_streams, newStreams.size, newStreams.size
@ -59,12 +55,6 @@ class NotificationHelper(val context: Context) {
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE) .setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setSmallIcon(R.drawable.ic_newpipe_triangle_white) .setSmallIcon(R.drawable.ic_newpipe_triangle_white)
.setLargeIcon(
BitmapFactory.decodeResource(
context.resources,
R.drawable.ic_newpipe_triangle_white
)
)
.setColor(ContextCompat.getColor(context, R.color.ic_launcher_background)) .setColor(ContextCompat.getColor(context, R.color.ic_launcher_background))
.setColorized(true) .setColorized(true)
.setAutoCancel(true) .setAutoCancel(true)
@ -87,19 +77,17 @@ class NotificationHelper(val context: Context) {
NavigationHelper NavigationHelper
.getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url) .getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
PendingIntent.FLAG_IMMUTABLE
else
0 0
) )
) )
return Single.create(NotificationIcon(context, data.avatarUrl)) PicassoHelper.loadNotificationIcon(data.avatarUrl, context) { bitmap ->
.subscribeOn(Schedulers.io()) builder.setLargeIcon(bitmap)
.observeOn(AndroidSchedulers.mainThread()) manager.notify(data.pseudoId, builder.build())
.doOnSuccess { icon ->
builder.setLargeIcon(icon)
} }
.ignoreElement()
.onErrorComplete()
.doOnComplete { manager.notify(data.pseudoId, builder.build()) }
} }
companion object { companion object {

View file

@ -1,60 +0,0 @@
package org.schabi.newpipe.local.feed.notifications
import android.app.ActivityManager
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import com.squareup.picasso.Picasso
import com.squareup.picasso.Target
import io.reactivex.rxjava3.core.SingleEmitter
import io.reactivex.rxjava3.core.SingleOnSubscribe
import org.schabi.newpipe.util.PicassoHelper
/**
* Helper class to handle loading and resizing of icons
* which are used going to be used in notifications.
*/
internal class NotificationIcon(
context: Context,
private val url: String,
) : SingleOnSubscribe<Bitmap> {
private val size = getIconSize(context)
override fun subscribe(emitter: SingleEmitter<Bitmap>) {
val target = SingleEmitterTarget(emitter)
PicassoHelper.loadThumbnail(url)
.resize(size, size)
.centerCrop()
.into(target)
emitter.setCancellable {
PicassoHelper.cancelRequest(target)
}
}
private class SingleEmitterTarget(private val emitter: SingleEmitter<Bitmap>) : Target {
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom?) {
if (!emitter.isDisposed) {
emitter.onSuccess(bitmap)
}
}
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable?) {
emitter.tryOnError(e)
}
override fun onPrepareLoad(placeHolderDrawable: Drawable?) = Unit
}
private companion object {
fun getIconSize(context: Context): Int {
val activityManager = context.getSystemService(
Context.ACTIVITY_SERVICE
) as ActivityManager?
val size1 = activityManager?.launcherLargeIconSize ?: 0
val size2 = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
return maxOf(size2, size1)
}
}
}

View file

@ -1,6 +1,7 @@
package org.schabi.newpipe.local.feed.notifications package org.schabi.newpipe.local.feed.notifications
import android.content.Context import android.content.Context
import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.work.BackoffPolicy import androidx.work.BackoffPolicy
import androidx.work.Constraints import androidx.work.Constraints
@ -12,7 +13,7 @@ import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import androidx.work.rxjava3.RxWorker import androidx.work.rxjava3.RxWorker
import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.core.Single
import org.schabi.newpipe.App import org.schabi.newpipe.App
import org.schabi.newpipe.R import org.schabi.newpipe.R
@ -34,30 +35,39 @@ class NotificationWorker(
} }
private val feedLoadManager = FeedLoadManager(appContext) private val feedLoadManager = FeedLoadManager(appContext)
override fun createWork(): Single<Result> = if (isEnabled(applicationContext)) { override fun createWork(): Single<Result> = if (areNotificationsEnabled(applicationContext)) {
feedLoadManager.startLoading( feedLoadManager.startLoading(
ignoreOutdatedThreshold = true, ignoreOutdatedThreshold = true,
groupId = FeedLoadManager.GROUP_NOTIFICATION_ENABLED groupId = FeedLoadManager.GROUP_NOTIFICATION_ENABLED
) )
.doOnSubscribe { showLoadingFeedForegroundNotification() }
.map { feed -> .map { feed ->
feed.mapNotNull { x -> // filter out feedUpdateInfo items (i.e. channels) with nothing new
x.value?.takeIf { feed.mapNotNull {
it.newStreams.isNotEmpty() it.value?.takeIf { feedUpdateInfo ->
feedUpdateInfo.newStreams.isNotEmpty()
} }
} }
} }
.doOnSubscribe { setForegroundAsync(createForegroundInfo()) } .observeOn(AndroidSchedulers.mainThread()) // Picasso requires calls from main thread
.flatMapObservable { Observable.fromIterable(it) } .map { feedUpdateInfoList ->
.flatMapCompletable { x -> notificationHelper.displayNewStreamsNotification(x) } // display notifications for each feedUpdateInfo (i.e. channel)
.toSingleDefault(Result.success()) feedUpdateInfoList.forEach { feedUpdateInfo ->
notificationHelper.displayNewStreamsNotification(feedUpdateInfo)
}
return@map Result.success()
}
.doOnError { throwable ->
Log.e(TAG, "Error while displaying streams notifications", throwable)
// TODO show error notification
}
.onErrorReturnItem(Result.failure()) .onErrorReturnItem(Result.failure())
} else { } else {
// Can be the case when the user disables notifications for NewPipe // the user can disable streams notifications in the device's app settings
// in the device's app settings.
Single.just(Result.success()) Single.just(Result.success())
} }
private fun createForegroundInfo(): ForegroundInfo { private fun showLoadingFeedForegroundNotification() {
val notification = NotificationCompat.Builder( val notification = NotificationCompat.Builder(
applicationContext, applicationContext,
applicationContext.getString(R.string.notification_channel_id) applicationContext.getString(R.string.notification_channel_id)
@ -68,14 +78,15 @@ class NotificationWorker(
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.setContentTitle(applicationContext.getString(R.string.feed_notification_loading)) .setContentTitle(applicationContext.getString(R.string.feed_notification_loading))
.build() .build()
return ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification) setForegroundAsync(ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification))
} }
companion object { companion object {
private const val TAG = App.PACKAGE_NAME + "_streams_notifications" private val TAG = NotificationWorker::class.java.simpleName
private const val WORK_TAG = App.PACKAGE_NAME + "_streams_notifications"
private fun isEnabled(context: Context) = private fun areNotificationsEnabled(context: Context) =
NotificationHelper.areNewStreamsNotificationsEnabled(context) && NotificationHelper.areNewStreamsNotificationsEnabled(context) &&
NotificationHelper.areNotificationsEnabledOnDevice(context) NotificationHelper.areNotificationsEnabledOnDevice(context)
@ -86,7 +97,7 @@ class NotificationWorker(
*/ */
@JvmStatic @JvmStatic
fun initialize(context: Context) { fun initialize(context: Context) {
if (isEnabled(context)) { if (areNotificationsEnabled(context)) {
schedule(context) schedule(context)
} else { } else {
cancel(context) cancel(context)
@ -114,13 +125,13 @@ class NotificationWorker(
options.interval, options.interval,
TimeUnit.MILLISECONDS TimeUnit.MILLISECONDS
).setConstraints(constraints) ).setConstraints(constraints)
.addTag(TAG) .addTag(WORK_TAG)
.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES) .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
.build() .build()
WorkManager.getInstance(context) WorkManager.getInstance(context)
.enqueueUniquePeriodicWork( .enqueueUniquePeriodicWork(
TAG, WORK_TAG,
if (force) { if (force) {
ExistingPeriodicWorkPolicy.REPLACE ExistingPeriodicWorkPolicy.REPLACE
} else { } else {
@ -139,7 +150,7 @@ class NotificationWorker(
@JvmStatic @JvmStatic
fun runNow(context: Context) { fun runNow(context: Context) {
val request = OneTimeWorkRequestBuilder<NotificationWorker>() val request = OneTimeWorkRequestBuilder<NotificationWorker>()
.addTag(TAG) .addTag(WORK_TAG)
.build() .build()
WorkManager.getInstance(context).enqueue(request) WorkManager.getInstance(context).enqueue(request)
} }
@ -149,7 +160,7 @@ class NotificationWorker(
*/ */
@JvmStatic @JvmStatic
fun cancel(context: Context) { fun cancel(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG) WorkManager.getInstance(context).cancelAllWorkByTag(WORK_TAG)
} }
} }
} }

View file

@ -5,6 +5,8 @@ import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import com.squareup.picasso.Cache; import com.squareup.picasso.Cache;
import com.squareup.picasso.LruCache; import com.squareup.picasso.LruCache;
@ -19,6 +21,7 @@ import org.schabi.newpipe.R;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
@ -161,6 +164,29 @@ public final class PicassoHelper {
} }
public static void loadNotificationIcon(final String url,
final Context context,
final Consumer<Bitmap> bitmapConsumer) {
loadImageDefault(url, R.drawable.ic_newpipe_triangle_white)
.into(new Target() {
@Override
public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) {
bitmapConsumer.accept(bitmap);
}
@Override
public void onBitmapFailed(final Exception e, final Drawable errorDrawable) {
bitmapConsumer.accept(BitmapFactory.decodeResource(context.getResources(),
R.drawable.ic_newpipe_triangle_white));
}
@Override
public void onPrepareLoad(final Drawable placeHolderDrawable) {
}
});
}
private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { private static RequestCreator loadImageDefault(final String url, final int placeholderResId) {
if (!shouldLoadImages || isBlank(url)) { if (!shouldLoadImages || isBlank(url)) {
return picassoInstance return picassoInstance