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.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.provider.Settings
@ -12,14 +11,11 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
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.extractor.stream.StreamInfoItem
import org.schabi.newpipe.local.feed.service.FeedUpdateInfo
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.
@ -34,7 +30,7 @@ class NotificationHelper(val context: Context) {
* Show a notification about new streams from a single channel.
* 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 summary = context.resources.getQuantityString(
R.plurals.new_streams, newStreams.size, newStreams.size
@ -59,12 +55,6 @@ class NotificationHelper(val context: Context) {
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.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))
.setColorized(true)
.setAutoCancel(true)
@ -87,19 +77,17 @@ class NotificationHelper(val context: Context) {
NavigationHelper
.getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
PendingIntent.FLAG_IMMUTABLE
else
0
)
)
return Single.create(NotificationIcon(context, data.avatarUrl))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { icon ->
builder.setLargeIcon(icon)
PicassoHelper.loadNotificationIcon(data.avatarUrl, context) { bitmap ->
builder.setLargeIcon(bitmap)
manager.notify(data.pseudoId, builder.build())
}
.ignoreElement()
.onErrorComplete()
.doOnComplete { manager.notify(data.pseudoId, builder.build()) }
}
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
import android.content.Context
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.work.BackoffPolicy
import androidx.work.Constraints
@ -12,7 +13,7 @@ import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkerParameters
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 org.schabi.newpipe.App
import org.schabi.newpipe.R
@ -34,30 +35,39 @@ class NotificationWorker(
}
private val feedLoadManager = FeedLoadManager(appContext)
override fun createWork(): Single<Result> = if (isEnabled(applicationContext)) {
override fun createWork(): Single<Result> = if (areNotificationsEnabled(applicationContext)) {
feedLoadManager.startLoading(
ignoreOutdatedThreshold = true,
groupId = FeedLoadManager.GROUP_NOTIFICATION_ENABLED
)
.doOnSubscribe { showLoadingFeedForegroundNotification() }
.map { feed ->
feed.mapNotNull { x ->
x.value?.takeIf {
it.newStreams.isNotEmpty()
// filter out feedUpdateInfo items (i.e. channels) with nothing new
feed.mapNotNull {
it.value?.takeIf { feedUpdateInfo ->
feedUpdateInfo.newStreams.isNotEmpty()
}
}
}
.doOnSubscribe { setForegroundAsync(createForegroundInfo()) }
.flatMapObservable { Observable.fromIterable(it) }
.flatMapCompletable { x -> notificationHelper.displayNewStreamsNotification(x) }
.toSingleDefault(Result.success())
.observeOn(AndroidSchedulers.mainThread()) // Picasso requires calls from main thread
.map { feedUpdateInfoList ->
// display notifications for each feedUpdateInfo (i.e. channel)
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())
} else {
// Can be the case when the user disables notifications for NewPipe
// in the device's app settings.
// the user can disable streams notifications in the device's app settings
Single.just(Result.success())
}
private fun createForegroundInfo(): ForegroundInfo {
private fun showLoadingFeedForegroundNotification() {
val notification = NotificationCompat.Builder(
applicationContext,
applicationContext.getString(R.string.notification_channel_id)
@ -68,14 +78,15 @@ class NotificationWorker(
.setPriority(NotificationCompat.PRIORITY_LOW)
.setContentTitle(applicationContext.getString(R.string.feed_notification_loading))
.build()
return ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification)
setForegroundAsync(ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification))
}
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.areNotificationsEnabledOnDevice(context)
@ -86,7 +97,7 @@ class NotificationWorker(
*/
@JvmStatic
fun initialize(context: Context) {
if (isEnabled(context)) {
if (areNotificationsEnabled(context)) {
schedule(context)
} else {
cancel(context)
@ -114,13 +125,13 @@ class NotificationWorker(
options.interval,
TimeUnit.MILLISECONDS
).setConstraints(constraints)
.addTag(TAG)
.addTag(WORK_TAG)
.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(context)
.enqueueUniquePeriodicWork(
TAG,
WORK_TAG,
if (force) {
ExistingPeriodicWorkPolicy.REPLACE
} else {
@ -139,7 +150,7 @@ class NotificationWorker(
@JvmStatic
fun runNow(context: Context) {
val request = OneTimeWorkRequestBuilder<NotificationWorker>()
.addTag(TAG)
.addTag(WORK_TAG)
.build()
WorkManager.getInstance(context).enqueue(request)
}
@ -149,7 +160,7 @@ class NotificationWorker(
*/
@JvmStatic
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.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Drawable;
import com.squareup.picasso.Cache;
import com.squareup.picasso.LruCache;
@ -19,6 +21,7 @@ import org.schabi.newpipe.R;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
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) {
if (!shouldLoadImages || isBlank(url)) {
return picassoInstance