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