Merge pull request #10078 from Isira-Seneviratne/Improve_feed_notifications
Improve new stream notifications
@ -1,6 +1,8 @@
|
|||||||
package org.schabi.newpipe.local.feed.notifications
|
package org.schabi.newpipe.local.feed.notifications
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
@ -12,13 +14,13 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import com.squareup.picasso.Target
|
import com.squareup.picasso.Target
|
||||||
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.Localization
|
|
||||||
import org.schabi.newpipe.util.NavigationHelper
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
import org.schabi.newpipe.util.PicassoHelper
|
import org.schabi.newpipe.util.PicassoHelper
|
||||||
|
|
||||||
@ -26,32 +28,27 @@ 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.
|
||||||
*/
|
*/
|
||||||
class NotificationHelper(val context: Context) {
|
class NotificationHelper(val context: Context) {
|
||||||
|
private val manager = NotificationManagerCompat.from(context)
|
||||||
private val manager = context.getSystemService(
|
|
||||||
Context.NOTIFICATION_SERVICE
|
|
||||||
) as NotificationManager
|
|
||||||
|
|
||||||
private val iconLoadingTargets = ArrayList<Target>()
|
private val iconLoadingTargets = ArrayList<Target>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a notification about new streams from a single channel.
|
* Show notifications for new streams from a single channel. The individual notifications are
|
||||||
* Opening the notification will open the corresponding channel page.
|
* expandable on Android 7.0 and later.
|
||||||
|
*
|
||||||
|
* Opening the summary notification will open the corresponding channel page. Opening the
|
||||||
|
* individual notifications will open the corresponding video.
|
||||||
*/
|
*/
|
||||||
fun displayNewStreamsNotification(data: FeedUpdateInfo) {
|
fun displayNewStreamsNotifications(data: FeedUpdateInfo) {
|
||||||
val newStreams: List<StreamInfoItem> = data.newStreams
|
val newStreams = 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
|
||||||
)
|
)
|
||||||
val builder = NotificationCompat.Builder(
|
val summaryBuilder = NotificationCompat.Builder(
|
||||||
context,
|
context,
|
||||||
context.getString(R.string.streams_notification_channel_id)
|
context.getString(R.string.streams_notification_channel_id)
|
||||||
)
|
)
|
||||||
.setContentTitle(Localization.concatenateStrings(data.name, summary))
|
.setContentTitle(data.name)
|
||||||
.setContentText(
|
.setContentText(summary)
|
||||||
data.listInfo.relatedItems.joinToString(
|
|
||||||
context.getString(R.string.enumeration_comma)
|
|
||||||
) { x -> x.name }
|
|
||||||
)
|
|
||||||
.setNumber(newStreams.size)
|
.setNumber(newStreams.size)
|
||||||
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
|
.setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE)
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
@ -60,16 +57,18 @@ class NotificationHelper(val context: Context) {
|
|||||||
.setColorized(true)
|
.setColorized(true)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||||
|
.setGroupSummary(true)
|
||||||
|
.setGroup(data.listInfo.url)
|
||||||
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
|
|
||||||
// Build style
|
// Build a summary notification for Android versions < 7.0
|
||||||
val style = NotificationCompat.InboxStyle()
|
val style = NotificationCompat.InboxStyle()
|
||||||
|
.setBigContentTitle(data.name)
|
||||||
newStreams.forEach { style.addLine(it.name) }
|
newStreams.forEach { style.addLine(it.name) }
|
||||||
style.setSummaryText(summary)
|
summaryBuilder.setStyle(style)
|
||||||
style.setBigContentTitle(data.name)
|
|
||||||
builder.setStyle(style)
|
|
||||||
|
|
||||||
// open the channel page when clicking on the notification
|
// open the channel page when clicking on the summary notification
|
||||||
builder.setContentIntent(
|
summaryBuilder.setContentIntent(
|
||||||
PendingIntentCompat.getActivity(
|
PendingIntentCompat.getActivity(
|
||||||
context,
|
context,
|
||||||
data.pseudoId,
|
data.pseudoId,
|
||||||
@ -84,13 +83,23 @@ class NotificationHelper(val context: Context) {
|
|||||||
// a Target is like a listener for image loading events
|
// a Target is like a listener for image loading events
|
||||||
val target = object : Target {
|
val target = object : Target {
|
||||||
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
|
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
|
||||||
builder.setLargeIcon(bitmap) // set only if there is actually one
|
// set channel icon only if there is actually one (for Android versions < 7.0)
|
||||||
manager.notify(data.pseudoId, builder.build())
|
summaryBuilder.setLargeIcon(bitmap)
|
||||||
|
|
||||||
|
// Show individual stream notifications, set channel icon only if there is actually
|
||||||
|
// one
|
||||||
|
showStreamNotifications(newStreams, data.listInfo.serviceId, bitmap)
|
||||||
|
// Show summary notification
|
||||||
|
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||||
|
|
||||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
|
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
|
||||||
manager.notify(data.pseudoId, builder.build())
|
// Show individual stream notifications
|
||||||
|
showStreamNotifications(newStreams, data.listInfo.serviceId, null)
|
||||||
|
// Show summary notification
|
||||||
|
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +115,49 @@ class NotificationHelper(val context: Context) {
|
|||||||
PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target)
|
PicassoHelper.loadNotificationIcon(data.avatarUrl).into(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showStreamNotifications(
|
||||||
|
newStreams: List<StreamInfoItem>,
|
||||||
|
serviceId: Int,
|
||||||
|
channelIcon: Bitmap?
|
||||||
|
) {
|
||||||
|
for (stream in newStreams) {
|
||||||
|
val notification = createStreamNotification(stream, serviceId, channelIcon)
|
||||||
|
manager.notify(stream.url.hashCode(), notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createStreamNotification(
|
||||||
|
item: StreamInfoItem,
|
||||||
|
serviceId: Int,
|
||||||
|
channelIcon: Bitmap?
|
||||||
|
): Notification {
|
||||||
|
return NotificationCompat.Builder(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.streams_notification_channel_id)
|
||||||
|
)
|
||||||
|
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||||
|
.setLargeIcon(channelIcon)
|
||||||
|
.setContentTitle(item.name)
|
||||||
|
.setContentText(item.uploaderName)
|
||||||
|
.setGroup(item.uploaderUrl)
|
||||||
|
.setColor(ContextCompat.getColor(context, R.color.ic_launcher_background))
|
||||||
|
.setColorized(true)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||||
|
.setContentIntent(
|
||||||
|
// Open the stream link in the player when clicking on the notification.
|
||||||
|
PendingIntentCompat.getActivity(
|
||||||
|
context,
|
||||||
|
item.url.hashCode(),
|
||||||
|
NavigationHelper.getStreamIntent(context, serviceId, item.url, item.name),
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setSilent(true) // Avoid creating noise for individual stream notifications.
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Check whether notifications are enabled on the device.
|
* Check whether notifications are enabled on the device.
|
||||||
@ -124,9 +176,7 @@ class NotificationHelper(val context: Context) {
|
|||||||
fun areNotificationsEnabledOnDevice(context: Context): Boolean {
|
fun areNotificationsEnabledOnDevice(context: Context): Boolean {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val channelId = context.getString(R.string.streams_notification_channel_id)
|
val channelId = context.getString(R.string.streams_notification_channel_id)
|
||||||
val manager = context.getSystemService(
|
val manager = context.getSystemService<NotificationManager>()!!
|
||||||
Context.NOTIFICATION_SERVICE
|
|
||||||
) as NotificationManager
|
|
||||||
val enabled = manager.areNotificationsEnabled()
|
val enabled = manager.areNotificationsEnabled()
|
||||||
val channel = manager.getNotificationChannel(channelId)
|
val channel = manager.getNotificationChannel(channelId)
|
||||||
val importance = channel?.importance
|
val importance = channel?.importance
|
||||||
|
@ -55,7 +55,7 @@ class NotificationWorker(
|
|||||||
.map { feedUpdateInfoList ->
|
.map { feedUpdateInfoList ->
|
||||||
// display notifications for each feedUpdateInfo (i.e. channel)
|
// display notifications for each feedUpdateInfo (i.e. channel)
|
||||||
feedUpdateInfoList.forEach { feedUpdateInfo ->
|
feedUpdateInfoList.forEach { feedUpdateInfo ->
|
||||||
notificationHelper.displayNewStreamsNotification(feedUpdateInfo)
|
notificationHelper.displayNewStreamsNotifications(feedUpdateInfo)
|
||||||
}
|
}
|
||||||
return@map Result.success()
|
return@map Result.success()
|
||||||
}
|
}
|
||||||
|
@ -563,11 +563,8 @@ public final class NavigationHelper {
|
|||||||
@Nullable final PlayQueue playQueue,
|
@Nullable final PlayQueue playQueue,
|
||||||
final boolean switchingPlayers) {
|
final boolean switchingPlayers) {
|
||||||
|
|
||||||
final Intent intent = getOpenIntent(context, url, serviceId,
|
final Intent intent = getStreamIntent(context, serviceId, url, title)
|
||||||
StreamingService.LinkType.STREAM);
|
.putExtra(VideoDetailFragment.KEY_SWITCHING_PLAYERS, switchingPlayers);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
intent.putExtra(Constants.KEY_TITLE, title);
|
|
||||||
intent.putExtra(VideoDetailFragment.KEY_SWITCHING_PLAYERS, switchingPlayers);
|
|
||||||
|
|
||||||
if (playQueue != null) {
|
if (playQueue != null) {
|
||||||
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
|
final String cacheKey = SerializedCache.getInstance().put(playQueue, PlayQueue.class);
|
||||||
@ -680,6 +677,15 @@ public final class NavigationHelper {
|
|||||||
return getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL);
|
return getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Intent getStreamIntent(final Context context,
|
||||||
|
final int serviceId,
|
||||||
|
final String url,
|
||||||
|
@Nullable final String title) {
|
||||||
|
return getOpenIntent(context, url, serviceId, StreamingService.LinkType.STREAM)
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra(Constants.KEY_TITLE, title);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finish this <code>Activity</code> as well as all <code>Activities</code> running below it
|
* Finish this <code>Activity</code> as well as all <code>Activities</code> running below it
|
||||||
* and then start <code>MainActivity</code>.
|
* and then start <code>MainActivity</code>.
|
||||||
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 480 B |
Before Width: | Height: | Size: 465 B After Width: | Height: | Size: 831 B |
Before Width: | Height: | Size: 823 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 802 B After Width: | Height: | Size: 1.5 KiB |