Group tracker notifications
parent
448c688629
commit
3affec0f88
@ -1,127 +0,0 @@
|
||||
package org.koitharu.kotatsu.tracker.work
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationChannelGroupCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
class TrackerNotificationChannels @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val settings: AppSettings,
|
||||
) {
|
||||
|
||||
private val manager = NotificationManagerCompat.from(context)
|
||||
|
||||
val areNotificationsDisabled: Boolean
|
||||
get() = !manager.areNotificationsEnabled()
|
||||
|
||||
fun updateChannels(categories: Collection<FavouriteCategory>) {
|
||||
manager.deleteNotificationChannel(OLD_CHANNEL_ID)
|
||||
val group = createGroup()
|
||||
val existingChannels = group.channels.associateByTo(HashMap()) { it.id }
|
||||
for (category in categories) {
|
||||
val id = getFavouritesChannelId(category.id)
|
||||
if (existingChannels.remove(id)?.name == category.title) {
|
||||
continue
|
||||
}
|
||||
val channel = NotificationChannelCompat.Builder(id, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(category.title)
|
||||
.setGroup(GROUP_ID)
|
||||
.build()
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
existingChannels.remove(CHANNEL_ID_HISTORY)
|
||||
createHistoryChannel()
|
||||
for (id in existingChannels.keys) {
|
||||
manager.deleteNotificationChannel(id)
|
||||
}
|
||||
}
|
||||
|
||||
fun createChannel(category: FavouriteCategory) {
|
||||
val id = getFavouritesChannelId(category.id)
|
||||
val channel = NotificationChannelCompat.Builder(id, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(category.title)
|
||||
.setGroup(createGroup().id)
|
||||
.build()
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
fun deleteChannel(categoryId: Long) {
|
||||
manager.deleteNotificationChannel(getFavouritesChannelId(categoryId))
|
||||
}
|
||||
|
||||
fun isFavouriteNotificationsEnabled(category: FavouriteCategory): Boolean {
|
||||
if (!manager.areNotificationsEnabled()) {
|
||||
return false
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = manager.getNotificationChannel(getFavouritesChannelId(category.id))
|
||||
channel != null && channel.importance != NotificationManager.IMPORTANCE_NONE
|
||||
} else {
|
||||
// fallback
|
||||
settings.isTrackerNotificationsEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun isHistoryNotificationsEnabled(): Boolean {
|
||||
if (!manager.areNotificationsEnabled()) {
|
||||
return false
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = manager.getNotificationChannel(getHistoryChannelId())
|
||||
channel != null && channel.importance != NotificationManager.IMPORTANCE_NONE
|
||||
} else {
|
||||
// fallback
|
||||
settings.isTrackerNotificationsEnabled
|
||||
}
|
||||
}
|
||||
|
||||
fun isNotificationGroupEnabled(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return settings.isTrackerNotificationsEnabled
|
||||
}
|
||||
val group = manager.getNotificationChannelGroupCompat(GROUP_ID) ?: return true
|
||||
return !group.isBlocked && group.channels.any { it.importance != NotificationManagerCompat.IMPORTANCE_NONE }
|
||||
}
|
||||
|
||||
fun getFavouritesChannelId(categoryId: Long): String {
|
||||
return CHANNEL_ID_PREFIX + categoryId
|
||||
}
|
||||
|
||||
fun getHistoryChannelId(): String {
|
||||
return CHANNEL_ID_HISTORY
|
||||
}
|
||||
|
||||
private fun createGroup(): NotificationChannelGroupCompat {
|
||||
return manager.getNotificationChannelGroupCompat(GROUP_ID) ?: run {
|
||||
val group = NotificationChannelGroupCompat.Builder(GROUP_ID)
|
||||
.setName(context.getString(R.string.new_chapters))
|
||||
.build()
|
||||
manager.createNotificationChannelGroup(group)
|
||||
group
|
||||
}
|
||||
}
|
||||
|
||||
private fun createHistoryChannel() {
|
||||
val channel = NotificationChannelCompat.Builder(CHANNEL_ID_HISTORY, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(context.getString(R.string.history))
|
||||
.setGroup(GROUP_ID)
|
||||
.build()
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val GROUP_ID = "trackers"
|
||||
private const val CHANNEL_ID_PREFIX = "track_fav_"
|
||||
private const val CHANNEL_ID_HISTORY = "track_history"
|
||||
private const val OLD_CHANNEL_ID = "tracking"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
package org.koitharu.kotatsu.tracker.work
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.VISIBILITY_PUBLIC
|
||||
import androidx.core.app.NotificationCompat.VISIBILITY_SECRET
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity
|
||||
import javax.inject.Inject
|
||||
|
||||
class TrackerNotificationHelper @Inject constructor(
|
||||
@ApplicationContext private val applicationContext: Context,
|
||||
private val settings: AppSettings,
|
||||
private val coil: ImageLoader,
|
||||
) {
|
||||
|
||||
fun getAreNotificationsEnabled(): Boolean {
|
||||
val manager = NotificationManagerCompat.from(applicationContext)
|
||||
if (!manager.areNotificationsEnabled()) {
|
||||
return false
|
||||
}
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = manager.getNotificationChannel(CHANNEL_ID)
|
||||
channel != null && channel.importance != NotificationManager.IMPORTANCE_NONE
|
||||
} else {
|
||||
// fallback
|
||||
settings.isTrackerNotificationsEnabled
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createNotification(manga: Manga, newChapters: List<MangaChapter>): NotificationInfo? {
|
||||
if (newChapters.isEmpty() || !applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||
return null
|
||||
}
|
||||
val id = manga.url.hashCode()
|
||||
val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||
val summary = applicationContext.resources.getQuantityString(
|
||||
R.plurals.new_chapters,
|
||||
newChapters.size,
|
||||
newChapters.size,
|
||||
)
|
||||
with(builder) {
|
||||
setContentText(summary)
|
||||
setContentTitle(manga.title)
|
||||
setNumber(newChapters.size)
|
||||
setLargeIcon(
|
||||
coil.execute(
|
||||
ImageRequest.Builder(applicationContext)
|
||||
.data(manga.coverUrl)
|
||||
.tag(manga.source)
|
||||
.build(),
|
||||
).toBitmapOrNull(),
|
||||
)
|
||||
setSmallIcon(R.drawable.ic_stat_book_plus)
|
||||
setGroup(GROUP_NEW_CHAPTERS)
|
||||
val style = NotificationCompat.InboxStyle(this)
|
||||
for (chapter in newChapters) {
|
||||
style.addLine(chapter.name)
|
||||
}
|
||||
style.setSummaryText(manga.title)
|
||||
style.setBigContentTitle(summary)
|
||||
setStyle(style)
|
||||
val intent = DetailsActivity.newIntent(applicationContext, manga)
|
||||
setContentIntent(
|
||||
PendingIntentCompat.getActivity(
|
||||
applicationContext,
|
||||
id,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
false,
|
||||
),
|
||||
)
|
||||
setVisibility(if (manga.isNsfw) VISIBILITY_SECRET else VISIBILITY_PUBLIC)
|
||||
setShortcutId(manga.id.toString())
|
||||
applyCommonSettings(this)
|
||||
}
|
||||
return NotificationInfo(id, TAG, builder.build(), manga, newChapters.size)
|
||||
}
|
||||
|
||||
fun createGroupNotification(
|
||||
notifications: List<NotificationInfo>
|
||||
): Notification? {
|
||||
if (notifications.size <= 1) {
|
||||
return null
|
||||
}
|
||||
val newChaptersCount = notifications.sumOf { it.newChapters }
|
||||
val builder = NotificationCompat.Builder(applicationContext, CHANNEL_ID)
|
||||
with(builder) {
|
||||
val title = applicationContext.resources.getQuantityString(
|
||||
R.plurals.new_chapters,
|
||||
newChaptersCount,
|
||||
newChaptersCount,
|
||||
)
|
||||
setContentTitle(title)
|
||||
setContentText(notifications.joinToString { it.manga.title })
|
||||
setSmallIcon(R.drawable.ic_stat_book_plus)
|
||||
val style = NotificationCompat.InboxStyle(this)
|
||||
for (item in notifications) {
|
||||
style.addLine(
|
||||
applicationContext.getString(R.string.new_chapters_pattern, item.manga.title, item.newChapters),
|
||||
)
|
||||
}
|
||||
style.setBigContentTitle(title)
|
||||
setStyle(style)
|
||||
setNumber(newChaptersCount)
|
||||
setGroup(GROUP_NEW_CHAPTERS)
|
||||
setGroupSummary(true)
|
||||
val intent = UpdatesActivity.newIntent(applicationContext)
|
||||
setContentIntent(
|
||||
PendingIntentCompat.getActivity(
|
||||
applicationContext,
|
||||
GROUP_NOTIFICATION_ID,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
false,
|
||||
),
|
||||
)
|
||||
applyCommonSettings(this)
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun updateChannels() {
|
||||
val manager = NotificationManagerCompat.from(applicationContext)
|
||||
manager.deleteNotificationChannel(LEGACY_CHANNEL_ID)
|
||||
manager.deleteNotificationChannel(LEGACY_CHANNEL_ID_HISTORY)
|
||||
manager.deleteNotificationChannelGroup(LEGACY_CHANNELS_GROUP_ID)
|
||||
|
||||
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(applicationContext.getString(R.string.new_chapters))
|
||||
.setDescription(applicationContext.getString(R.string.show_notification_new_chapters_on))
|
||||
.setShowBadge(true)
|
||||
.setLightColor(ContextCompat.getColor(applicationContext, R.color.blue_primary))
|
||||
.build()
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
private fun applyCommonSettings(builder: NotificationCompat.Builder) {
|
||||
builder.setAutoCancel(true)
|
||||
builder.setCategory(NotificationCompat.CATEGORY_SOCIAL)
|
||||
builder.priority = NotificationCompat.PRIORITY_DEFAULT
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
builder.setSound(settings.notificationSound)
|
||||
var defaults = if (settings.notificationLight) {
|
||||
builder.setLights(ContextCompat.getColor(applicationContext, R.color.blue_primary), 1000, 5000)
|
||||
NotificationCompat.DEFAULT_LIGHTS
|
||||
} else 0
|
||||
if (settings.notificationVibrate) {
|
||||
builder.setVibrate(longArrayOf(500, 500, 500, 500))
|
||||
defaults = defaults or NotificationCompat.DEFAULT_VIBRATE
|
||||
}
|
||||
builder.setDefaults(defaults)
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationInfo(
|
||||
val id: Int,
|
||||
val tag: String,
|
||||
val notification: Notification,
|
||||
val manga: Manga,
|
||||
val newChapters: Int,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
const val CHANNEL_ID = "tracker_chapters"
|
||||
const val GROUP_NOTIFICATION_ID = 0
|
||||
const val GROUP_NEW_CHAPTERS = "org.koitharu.kotatsu.NEW_CHAPTERS"
|
||||
const val TAG = "tracker"
|
||||
|
||||
private const val LEGACY_CHANNELS_GROUP_ID = "trackers"
|
||||
private const val LEGACY_CHANNEL_ID_PREFIX = "track_fav_"
|
||||
private const val LEGACY_CHANNEL_ID_HISTORY = "track_history"
|
||||
private const val LEGACY_CHANNEL_ID = "tracking"
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
package org.koitharu.kotatsu.tracker.work
|
||||
|
||||
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
|
||||
|
||||
data class TrackingItem(
|
||||
val tracking: MangaTracking,
|
||||
val channelId: String?,
|
||||
)
|
||||
Loading…
Reference in New Issue