Group tracker notifications

This commit is contained in:
Koitharu
2024-04-23 11:10:29 +03:00
parent 448c688629
commit 3affec0f88
9 changed files with 239 additions and 285 deletions

View File

@@ -21,13 +21,11 @@ import org.koitharu.kotatsu.favourites.data.toMangaList
import org.koitharu.kotatsu.favourites.domain.model.Cover
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
import javax.inject.Inject
@Reusable
class FavouritesRepository @Inject constructor(
private val db: MangaDatabase,
private val channels: TrackerNotificationChannels,
) {
suspend fun getAllManga(): List<Manga> {
@@ -145,7 +143,6 @@ class FavouritesRepository @Inject constructor(
)
val id = db.getFavouriteCategoriesDao().insert(entity)
val category = entity.toFavouriteCategory(id)
channels.createChannel(category)
return category
}
@@ -174,10 +171,6 @@ class FavouritesRepository @Inject constructor(
db.getFavouriteCategoriesDao().delete(id)
}
}
// run after transaction success
for (id in ids) {
channels.deleteChannel(id)
}
}
suspend fun setCategoryOrder(id: Long, order: ListSortOrder) {

View File

@@ -21,7 +21,7 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.settings.tracker.categories.TrackerCategoriesConfigSheet
import org.koitharu.kotatsu.settings.utils.DozeHelper
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
import org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper
import javax.inject.Inject
@AndroidEntryPoint
@@ -33,7 +33,7 @@ class TrackerSettingsFragment :
private val dozeHelper = DozeHelper(this)
@Inject
lateinit var channels: TrackerNotificationChannels
lateinit var notificationHelper: TrackerNotificationHelper
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_tracker)
@@ -88,7 +88,7 @@ class TrackerSettingsFragment :
true
}
channels.areNotificationsDisabled -> {
!notificationHelper.getAreNotificationsEnabled() -> {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", requireContext().packageName, null))
startActivitySafe(intent)
@@ -116,8 +116,7 @@ class TrackerSettingsFragment :
val pref = findPreference<Preference>(AppSettings.KEY_NOTIFICATIONS_SETTINGS) ?: return
pref.setSummary(
when {
channels.areNotificationsDisabled -> R.string.disabled
channels.isNotificationGroupEnabled() -> R.string.show_notification_new_chapters_on
notificationHelper.getAreNotificationsEnabled() -> R.string.show_notification_new_chapters_on
else -> R.string.show_notification_new_chapters_off
},
)

View File

@@ -15,8 +15,6 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
import org.koitharu.kotatsu.tracker.work.TrackerNotificationChannels
import org.koitharu.kotatsu.tracker.work.TrackingItem
import java.time.Instant
import javax.inject.Inject
import kotlin.contracts.InvocationKind
@@ -28,28 +26,12 @@ class Tracker @Inject constructor(
private val repository: TrackingRepository,
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val channels: TrackerNotificationChannels,
private val mangaRepositoryFactory: MangaRepository.Factory,
) {
suspend fun getTracks(limit: Int): List<TrackingItem> {
suspend fun getTracks(limit: Int): List<MangaTracking> {
repository.updateTracks()
return repository.getTracks(offset = 0, limit = limit).map {
val categoryId = repository.getCategoryId(it.manga.id)
TrackingItem(
tracking = it,
channelId = if (categoryId == NO_ID) {
channels.getHistoryChannelId()
} else {
channels.getFavouritesChannelId(categoryId)
},
)
}
}
suspend fun updateNotificationsChannels() {
val categories = favouritesRepository.getCategories()
channels.updateChannels(categories)
return repository.getTracks(offset = 0, limit = limit)
}
suspend fun gc() {
@@ -131,7 +113,7 @@ class Tracker @Inject constructor(
private fun compare(track: MangaTracking, manga: Manga, branch: String?): MangaUpdates.Success {
if (track.isEmpty()) {
// first check or manga was empty on last check
return MangaUpdates.Success(manga, emptyList(), isValid = false, channelId = null)
return MangaUpdates.Success(manga, emptyList(), isValid = false)
}
val chapters = requireNotNull(manga.getChapters(branch))
val newChapters = chapters.takeLastWhile { x -> x.id != track.lastChapterId }
@@ -141,16 +123,15 @@ class Tracker @Inject constructor(
manga = manga,
newChapters = emptyList(),
isValid = chapters.lastOrNull()?.id == track.lastChapterId,
channelId = null,
)
}
newChapters.size == chapters.size -> {
MangaUpdates.Success(manga, emptyList(), isValid = false, channelId = null)
MangaUpdates.Success(manga, emptyList(), isValid = false)
}
else -> {
MangaUpdates.Success(manga, newChapters, isValid = true, channelId = null)
MangaUpdates.Success(manga, newChapters, isValid = true)
}
}
}

View File

@@ -13,7 +13,6 @@ sealed interface MangaUpdates {
override val manga: Manga,
val newChapters: List<MangaChapter>,
val isValid: Boolean,
val channelId: String?,
) : MangaUpdates {
fun isNotEmpty() = newChapters.isNotEmpty()

View File

@@ -1,16 +1,13 @@
package org.koitharu.kotatsu.tracker.work
import android.app.PendingIntent
import android.content.Context
import android.content.pm.ServiceInfo
import android.os.Build
import androidx.annotation.CheckResult
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 androidx.hilt.work.HiltWorker
import androidx.work.BackoffPolicy
import androidx.work.Constraints
@@ -27,8 +24,6 @@ import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.WorkerParameters
import androidx.work.await
import coil.ImageLoader
import coil.request.ImageRequest
import dagger.Reusable
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
@@ -37,6 +32,7 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
@@ -53,17 +49,15 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
import org.koitharu.kotatsu.core.util.ext.onEachIndexed
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
import org.koitharu.kotatsu.core.util.ext.trySetForeground
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.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.parsers.util.toIntUp
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
import org.koitharu.kotatsu.tracker.domain.Tracker
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
import org.koitharu.kotatsu.tracker.work.TrackerNotificationHelper.NotificationInfo
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Provider
@@ -74,7 +68,7 @@ import com.google.android.material.R as materialR
class TrackWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
private val coil: ImageLoader,
private val notificationHelper: TrackerNotificationHelper,
private val settings: AppSettings,
private val tracker: Tracker,
private val workManager: WorkManager,
@@ -84,6 +78,7 @@ class TrackWorker @AssistedInject constructor(
private val notificationManager by lazy { NotificationManagerCompat.from(applicationContext) }
override suspend fun doWork(): Result {
notificationHelper.updateChannels()
val isForeground = trySetForeground()
logger.log("doWork(): attempt $runAttemptCount")
return try {
@@ -105,32 +100,33 @@ class TrackWorker @AssistedInject constructor(
if (!settings.isTrackerEnabled) {
return Result.success(workDataOf(0, 0))
}
tracker.updateNotificationsChannels()
val tracks = tracker.getTracks(if (isFullRun) Int.MAX_VALUE else BATCH_SIZE)
logger.log("Total ${tracks.size} tracks")
if (tracks.isEmpty()) {
return Result.success(workDataOf(0, 0))
}
checkUpdatesAsync(tracks)
val notifications = checkUpdatesAsync(tracks)
if (notifications.isNotEmpty() && applicationContext.checkNotificationPermission(null)) {
val groupNotification = notificationHelper.createGroupNotification(notifications)
notifications.forEach { notificationManager.notify(it.tag, it.id, it.notification) }
if (groupNotification != null) {
notificationManager.notify(TAG, TrackerNotificationHelper.GROUP_NOTIFICATION_ID, groupNotification)
}
}
return Result.success()
}
private suspend fun checkUpdatesAsync(tracks: List<TrackingItem>): List<MangaUpdates> {
@CheckResult
private suspend fun checkUpdatesAsync(tracks: List<MangaTracking>): List<NotificationInfo> {
val semaphore = Semaphore(MAX_PARALLELISM)
return channelFlow {
for ((track, channelId) in tracks) {
for (track in tracks) {
launch {
semaphore.withPermit {
send(
runCatchingCancellable {
tracker.fetchUpdates(track, commit = true).let {
if (it is MangaUpdates.Success) {
it.copy(channelId = channelId)
} else {
it
}
}
tracker.fetchUpdates(track, commit = true)
}.getOrElse { error ->
MangaUpdates.Failure(
manga = track.manga,
@@ -145,94 +141,26 @@ class TrackWorker @AssistedInject constructor(
if (applicationContext.checkNotificationPermission(WORKER_CHANNEL_ID)) {
notificationManager.notify(WORKER_NOTIFICATION_ID, createWorkerNotification(tracks.size, index + 1))
}
if (it is MangaUpdates.Failure) {
val e = it.error
logger.log("checkUpdatesAsync", e)
if (e is CloudFlareProtectedException) {
CaptchaNotifier(applicationContext).notify(e)
}
}
}.mapNotNull {
when (it) {
is MangaUpdates.Failure -> {
val e = it.error
logger.log("checkUpdatesAsync", e)
if (e is CloudFlareProtectedException) {
CaptchaNotifier(applicationContext).notify(e)
}
}
is MangaUpdates.Success -> {
if (it.isValid && it.isNotEmpty()) {
showNotification(
manga = it.manga,
channelId = it.channelId,
newChapters = it.newChapters,
)
}
is MangaUpdates.Failure -> null
is MangaUpdates.Success -> if (it.isValid && it.isNotEmpty()) {
notificationHelper.createNotification(
manga = it.manga,
newChapters = it.newChapters,
)
} else {
null
}
}
}.toList(ArrayList(tracks.size))
}
private suspend fun showNotification(
manga: Manga,
channelId: String?,
newChapters: List<MangaChapter>,
) {
if (newChapters.isEmpty() || channelId == null || !applicationContext.checkNotificationPermission(channelId)) {
return
}
val id = manga.url.hashCode()
val colorPrimary = ContextCompat.getColor(applicationContext, R.color.blue_primary)
val builder = NotificationCompat.Builder(applicationContext, channelId)
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,
),
)
setAutoCancel(true)
setCategory(NotificationCompat.CATEGORY_PROMO)
setVisibility(if (manga.isNsfw) VISIBILITY_SECRET else VISIBILITY_PUBLIC)
setShortcutId(manga.id.toString())
priority = NotificationCompat.PRIORITY_DEFAULT
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
builder.setSound(settings.notificationSound)
var defaults = if (settings.notificationLight) {
setLights(colorPrimary, 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)
}
}
notificationManager.notify(TAG, id, builder.build())
}.toList()
}
override suspend fun getForegroundInfo(): ForegroundInfo {
@@ -250,11 +178,7 @@ class TrackWorker @AssistedInject constructor(
val notification = createWorkerNotification(0, 0)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ForegroundInfo(
WORKER_NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC,
)
ForegroundInfo(WORKER_NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
} else {
ForegroundInfo(WORKER_NOTIFICATION_ID, notification)
}
@@ -367,7 +291,6 @@ class TrackWorker @AssistedInject constructor(
const val WORKER_CHANNEL_ID = "track_worker"
const val WORKER_NOTIFICATION_ID = 35
const val GROUP_NEW_CHAPTERS = "org.koitharu.kotatsu.NEW_CHAPTERS"
const val TAG = "tracking"
const val TAG_ONESHOT = "tracking_oneshot"
const val MAX_PARALLELISM = 6

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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?,
)

View File

@@ -633,4 +633,5 @@
<string name="less_frequently">Less frequently</string>
<string name="more_frequently">More frequently</string>
<string name="frequency_of_check">Frequency of check</string>
<string name="new_chapters_pattern">%1$s: %2$d</string>
</resources>