|
@@ -1,20 +1,11 @@
|
|
|
package eu.kanade.tachiyomi.data.library
|
|
|
|
|
|
-import android.app.Notification
|
|
|
-import android.app.PendingIntent
|
|
|
import android.app.Service
|
|
|
import android.content.Context
|
|
|
import android.content.Intent
|
|
|
-import android.graphics.Bitmap
|
|
|
-import android.graphics.BitmapFactory
|
|
|
import android.os.Build
|
|
|
import android.os.IBinder
|
|
|
import android.os.PowerManager
|
|
|
-import androidx.core.app.NotificationCompat
|
|
|
-import androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY
|
|
|
-import androidx.core.app.NotificationManagerCompat
|
|
|
-import com.bumptech.glide.Glide
|
|
|
-import eu.kanade.tachiyomi.R
|
|
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
|
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|
|
import eu.kanade.tachiyomi.data.database.models.Category
|
|
@@ -23,26 +14,17 @@ import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
|
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
|
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
|
|
-import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
|
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
|
|
-import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
|
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
|
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
|
import eu.kanade.tachiyomi.source.SourceManager
|
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
|
|
-import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
|
|
-import eu.kanade.tachiyomi.util.lang.chop
|
|
|
import eu.kanade.tachiyomi.util.prepUpdateCover
|
|
|
import eu.kanade.tachiyomi.util.system.acquireWakeLock
|
|
|
import eu.kanade.tachiyomi.util.system.isServiceRunning
|
|
|
-import eu.kanade.tachiyomi.util.system.notification
|
|
|
-import eu.kanade.tachiyomi.util.system.notificationBuilder
|
|
|
-import eu.kanade.tachiyomi.util.system.notificationManager
|
|
|
-import java.text.DecimalFormat
|
|
|
-import java.text.DecimalFormatSymbols
|
|
|
import java.util.ArrayList
|
|
|
import java.util.concurrent.atomic.AtomicInteger
|
|
|
import rx.Observable
|
|
@@ -74,39 +56,13 @@ class LibraryUpdateService(
|
|
|
*/
|
|
|
private lateinit var wakeLock: PowerManager.WakeLock
|
|
|
|
|
|
+ private lateinit var notifier: LibraryUpdateNotifier
|
|
|
+
|
|
|
/**
|
|
|
* Subscription where the update is done.
|
|
|
*/
|
|
|
private var subscription: Subscription? = null
|
|
|
|
|
|
- /**
|
|
|
- * Pending intent of action that cancels the library update
|
|
|
- */
|
|
|
- private val cancelIntent by lazy {
|
|
|
- NotificationReceiver.cancelLibraryUpdatePendingBroadcast(this)
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Bitmap of the app for notifications.
|
|
|
- */
|
|
|
- private val notificationBitmap by lazy {
|
|
|
- BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Cached progress notification to avoid creating a lot.
|
|
|
- */
|
|
|
- private val progressNotificationBuilder by lazy {
|
|
|
- notificationBuilder(Notifications.CHANNEL_LIBRARY) {
|
|
|
- setContentTitle(getString(R.string.app_name))
|
|
|
- setSmallIcon(R.drawable.ic_refresh_24dp)
|
|
|
- setLargeIcon(notificationBitmap)
|
|
|
- setOngoing(true)
|
|
|
- setOnlyAlertOnce(true)
|
|
|
- addAction(R.drawable.ic_close_24dp, getString(android.R.string.cancel), cancelIntent)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* Defines what should be updated within a service execution.
|
|
|
*/
|
|
@@ -128,10 +84,6 @@ class LibraryUpdateService(
|
|
|
*/
|
|
|
const val KEY_TARGET = "target"
|
|
|
|
|
|
- private const val NOTIF_MAX_CHAPTERS = 5
|
|
|
- private const val NOTIF_TITLE_MAX_LEN = 45
|
|
|
- private const val NOTIF_ICON_SIZE = 192
|
|
|
-
|
|
|
/**
|
|
|
* Returns the status of the service.
|
|
|
*
|
|
@@ -186,9 +138,10 @@ class LibraryUpdateService(
|
|
|
override fun onCreate() {
|
|
|
super.onCreate()
|
|
|
|
|
|
- startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotificationBuilder.build())
|
|
|
-
|
|
|
+ notifier = LibraryUpdateNotifier(this)
|
|
|
wakeLock = acquireWakeLock(javaClass.name)
|
|
|
+
|
|
|
+ startForeground(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build())
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -316,7 +269,7 @@ class LibraryUpdateService(
|
|
|
{ bySource ->
|
|
|
bySource
|
|
|
// Notify manga that will update.
|
|
|
- .doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
|
|
+ .doOnNext { notifier.showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
|
|
|
.concatMap { manga ->
|
|
|
updateManga(manga)
|
|
|
// If there's any error, return empty update and continue.
|
|
@@ -358,7 +311,7 @@ class LibraryUpdateService(
|
|
|
// Notify result of the overall update.
|
|
|
.doOnCompleted {
|
|
|
if (newUpdates.isNotEmpty()) {
|
|
|
- showUpdateNotifications(newUpdates)
|
|
|
+ notifier.showUpdateNotifications(newUpdates)
|
|
|
if (downloadNew && hasDownloads) {
|
|
|
DownloadService.start(this)
|
|
|
}
|
|
@@ -368,7 +321,7 @@ class LibraryUpdateService(
|
|
|
Timber.e("Failed updating: ${failedUpdates.map { it.title }}")
|
|
|
}
|
|
|
|
|
|
- cancelProgressNotification()
|
|
|
+ notifier.cancelProgressNotification()
|
|
|
}
|
|
|
.map { manga -> manga.first }
|
|
|
}
|
|
@@ -415,14 +368,14 @@ class LibraryUpdateService(
|
|
|
var count = 0
|
|
|
|
|
|
return Observable.from(mangaToUpdate)
|
|
|
- .doOnNext { showProgressNotification(it, count++, mangaToUpdate.size) }
|
|
|
+ .doOnNext { notifier.showProgressNotification(it, count++, mangaToUpdate.size) }
|
|
|
.map { manga ->
|
|
|
manga.prepUpdateCover(coverCache)
|
|
|
db.insertManga(manga).executeAsBlocking()
|
|
|
manga
|
|
|
}
|
|
|
.doOnCompleted {
|
|
|
- cancelProgressNotification()
|
|
|
+ notifier.cancelProgressNotification()
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -439,7 +392,7 @@ class LibraryUpdateService(
|
|
|
// Emit each manga and update it sequentially.
|
|
|
return Observable.from(mangaToUpdate)
|
|
|
// Notify manga that will update.
|
|
|
- .doOnNext { showProgressNotification(it, count++, mangaToUpdate.size) }
|
|
|
+ .doOnNext { notifier.showProgressNotification(it, count++, mangaToUpdate.size) }
|
|
|
// Update the tracking details.
|
|
|
.concatMap { manga ->
|
|
|
val tracks = db.getTracks(manga).executeAsBlocking()
|
|
@@ -458,207 +411,7 @@ class LibraryUpdateService(
|
|
|
.map { manga }
|
|
|
}
|
|
|
.doOnCompleted {
|
|
|
- cancelProgressNotification()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Shows the notification containing the currently updating manga and the progress.
|
|
|
- *
|
|
|
- * @param manga the manga that's being updated.
|
|
|
- * @param current the current progress.
|
|
|
- * @param total the total progress.
|
|
|
- */
|
|
|
- private fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
|
|
- val title = if (preferences.hideNotificationContent()) {
|
|
|
- getString(R.string.notification_check_updates)
|
|
|
- } else {
|
|
|
- manga.title
|
|
|
- }
|
|
|
-
|
|
|
- notificationManager.notify(
|
|
|
- Notifications.ID_LIBRARY_PROGRESS,
|
|
|
- progressNotificationBuilder
|
|
|
- .setContentTitle(title)
|
|
|
- .setProgress(total, current, false)
|
|
|
- .build()
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Shows the notification containing the result of the update done by the service.
|
|
|
- *
|
|
|
- * @param updates a list of manga with new updates.
|
|
|
- */
|
|
|
- private fun showUpdateNotifications(updates: List<Pair<Manga, Array<Chapter>>>) {
|
|
|
- if (updates.isEmpty()) {
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- NotificationManagerCompat.from(this).apply {
|
|
|
- // Parent group notification
|
|
|
- notify(
|
|
|
- Notifications.ID_NEW_CHAPTERS,
|
|
|
- notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
|
|
- setContentTitle(getString(R.string.notification_new_chapters))
|
|
|
- if (updates.size == 1 && !preferences.hideNotificationContent()) {
|
|
|
- setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
|
|
|
- } else {
|
|
|
- setContentText(resources.getQuantityString(R.plurals.notification_new_chapters_summary, updates.size, updates.size))
|
|
|
-
|
|
|
- if (!preferences.hideNotificationContent()) {
|
|
|
- setStyle(
|
|
|
- NotificationCompat.BigTextStyle().bigText(
|
|
|
- updates.joinToString("\n") {
|
|
|
- it.first.title.chop(NOTIF_TITLE_MAX_LEN)
|
|
|
- }
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- setSmallIcon(R.drawable.ic_tachi)
|
|
|
- setLargeIcon(notificationBitmap)
|
|
|
-
|
|
|
- setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
|
|
- setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
|
|
- setGroupSummary(true)
|
|
|
- priority = NotificationCompat.PRIORITY_HIGH
|
|
|
-
|
|
|
- setContentIntent(getNotificationIntent())
|
|
|
- setAutoCancel(true)
|
|
|
- }
|
|
|
- )
|
|
|
-
|
|
|
- // Per-manga notification
|
|
|
- if (!preferences.hideNotificationContent()) {
|
|
|
- updates.forEach {
|
|
|
- val (manga, chapters) = it
|
|
|
- notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters))
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun createNewChaptersNotification(manga: Manga, chapters: Array<Chapter>): Notification {
|
|
|
- return notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
|
|
- setContentTitle(manga.title)
|
|
|
-
|
|
|
- val description = getNewChaptersDescription(chapters)
|
|
|
- setContentText(description)
|
|
|
- setStyle(NotificationCompat.BigTextStyle().bigText(description))
|
|
|
-
|
|
|
- setSmallIcon(R.drawable.ic_tachi)
|
|
|
-
|
|
|
- val icon = getMangaIcon(manga)
|
|
|
- if (icon != null) {
|
|
|
- setLargeIcon(icon)
|
|
|
- }
|
|
|
-
|
|
|
- setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
|
|
- setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
|
|
- priority = NotificationCompat.PRIORITY_HIGH
|
|
|
-
|
|
|
- // Open first chapter on tap
|
|
|
- setContentIntent(NotificationReceiver.openChapterPendingActivity(this@LibraryUpdateService, manga, chapters.first()))
|
|
|
- setAutoCancel(true)
|
|
|
-
|
|
|
- // Mark chapters as read action
|
|
|
- addAction(
|
|
|
- R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
|
|
|
- NotificationReceiver.markAsReadPendingBroadcast(
|
|
|
- this@LibraryUpdateService,
|
|
|
- manga, chapters, Notifications.ID_NEW_CHAPTERS
|
|
|
- )
|
|
|
- )
|
|
|
- // View chapters action
|
|
|
- addAction(
|
|
|
- R.drawable.ic_book_24dp, getString(R.string.action_view_chapters),
|
|
|
- NotificationReceiver.openChapterPendingActivity(
|
|
|
- this@LibraryUpdateService,
|
|
|
- manga, Notifications.ID_NEW_CHAPTERS
|
|
|
- )
|
|
|
- )
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Cancels the progress notification.
|
|
|
- */
|
|
|
- private fun cancelProgressNotification() {
|
|
|
- notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
|
|
|
- }
|
|
|
-
|
|
|
- private fun getMangaIcon(manga: Manga): Bitmap? {
|
|
|
- return try {
|
|
|
- Glide.with(this)
|
|
|
- .asBitmap()
|
|
|
- .load(manga.toMangaThumbnail())
|
|
|
- .dontTransform()
|
|
|
- .centerCrop()
|
|
|
- .circleCrop()
|
|
|
- .override(NOTIF_ICON_SIZE, NOTIF_ICON_SIZE)
|
|
|
- .submit()
|
|
|
- .get()
|
|
|
- } catch (e: Exception) {
|
|
|
- null
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
|
|
|
- val formatter = DecimalFormat(
|
|
|
- "#.###",
|
|
|
- DecimalFormatSymbols()
|
|
|
- .apply { decimalSeparator = '.' }
|
|
|
- )
|
|
|
-
|
|
|
- val displayableChapterNumbers = chapters
|
|
|
- .filter { it.isRecognizedNumber }
|
|
|
- .sortedBy { it.chapter_number }
|
|
|
- .map { formatter.format(it.chapter_number) }
|
|
|
- .toSet()
|
|
|
-
|
|
|
- return when (displayableChapterNumbers.size) {
|
|
|
- // No sensible chapter numbers to show (i.e. no chapters have parsed chapter number)
|
|
|
- 0 -> {
|
|
|
- // "1 new chapter" or "5 new chapters"
|
|
|
- resources.getQuantityString(R.plurals.notification_chapters_generic, chapters.size, chapters.size)
|
|
|
- }
|
|
|
- // Only 1 chapter has a parsed chapter number
|
|
|
- 1 -> {
|
|
|
- val remaining = chapters.size - displayableChapterNumbers.size
|
|
|
- if (remaining == 0) {
|
|
|
- // "Chapter 2.5"
|
|
|
- resources.getString(R.string.notification_chapters_single, displayableChapterNumbers.first())
|
|
|
- } else {
|
|
|
- // "Chapter 2.5 and 10 more"
|
|
|
- resources.getString(R.string.notification_chapters_single_and_more, displayableChapterNumbers.first(), remaining)
|
|
|
- }
|
|
|
+ notifier.cancelProgressNotification()
|
|
|
}
|
|
|
- // Everything else (i.e. multiple parsed chapter numbers)
|
|
|
- else -> {
|
|
|
- val shouldTruncate = displayableChapterNumbers.size > NOTIF_MAX_CHAPTERS
|
|
|
- if (shouldTruncate) {
|
|
|
- // "Chapters 1, 2.5, 3, 4, 5 and 10 more"
|
|
|
- val remaining = displayableChapterNumbers.size - NOTIF_MAX_CHAPTERS
|
|
|
- val joinedChapterNumbers = displayableChapterNumbers.take(NOTIF_MAX_CHAPTERS).joinToString(", ")
|
|
|
- resources.getQuantityString(R.plurals.notification_chapters_multiple_and_more, remaining, joinedChapterNumbers, remaining)
|
|
|
- } else {
|
|
|
- // "Chapters 1, 2.5, 3"
|
|
|
- resources.getString(R.string.notification_chapters_multiple, displayableChapterNumbers.joinToString(", "))
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Returns an intent to open the main activity.
|
|
|
- */
|
|
|
- private fun getNotificationIntent(): PendingIntent {
|
|
|
- val intent = Intent(this, MainActivity::class.java).apply {
|
|
|
- flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
|
- action = MainActivity.SHORTCUT_RECENTLY_UPDATED
|
|
|
- }
|
|
|
- return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
|
}
|
|
|
}
|