mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 22:37:56 +01:00 
			
		
		
		
	Move notification logic out of LibraryUpdateService
This commit is contained in:
		| @@ -70,11 +70,11 @@ class BackupCreateService : Service() { | ||||
|  | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|  | ||||
|         notifier = BackupNotifier(this) | ||||
|         wakeLock = acquireWakeLock(javaClass.name) | ||||
|  | ||||
|         startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build()) | ||||
|  | ||||
|         wakeLock = acquireWakeLock(javaClass.name) | ||||
|     } | ||||
|  | ||||
|     override fun stopService(name: Intent?): Boolean { | ||||
|   | ||||
| @@ -124,11 +124,11 @@ class BackupRestoreService : Service() { | ||||
|  | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|  | ||||
|         notifier = BackupNotifier(this) | ||||
|         wakeLock = acquireWakeLock(javaClass.name) | ||||
|  | ||||
|         startForeground(Notifications.ID_RESTORE_PROGRESS, notifier.showRestoreProgress().build()) | ||||
|  | ||||
|         wakeLock = acquireWakeLock(javaClass.name) | ||||
|     } | ||||
|  | ||||
|     override fun stopService(name: Intent?): Boolean { | ||||
|   | ||||
| @@ -0,0 +1,268 @@ | ||||
| package eu.kanade.tachiyomi.data.library | ||||
|  | ||||
| import android.app.Notification | ||||
| import android.app.PendingIntent | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.graphics.Bitmap | ||||
| import android.graphics.BitmapFactory | ||||
| import androidx.core.app.NotificationCompat | ||||
| import androidx.core.app.NotificationManagerCompat | ||||
| import com.bumptech.glide.Glide | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.data.database.models.Chapter | ||||
| import eu.kanade.tachiyomi.data.database.models.Manga | ||||
| import eu.kanade.tachiyomi.data.glide.toMangaThumbnail | ||||
| 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.ui.main.MainActivity | ||||
| import eu.kanade.tachiyomi.util.lang.chop | ||||
| 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 uy.kohesive.injekt.injectLazy | ||||
|  | ||||
| class LibraryUpdateNotifier(private val context: Context) { | ||||
|  | ||||
|     private val preferences: PreferencesHelper by injectLazy() | ||||
|  | ||||
|     /** | ||||
|      * Pending intent of action that cancels the library update | ||||
|      */ | ||||
|     private val cancelIntent by lazy { | ||||
|         NotificationReceiver.cancelLibraryUpdatePendingBroadcast(context) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Bitmap of the app for notifications. | ||||
|      */ | ||||
|     private val notificationBitmap by lazy { | ||||
|         BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Cached progress notification to avoid creating a lot. | ||||
|      */ | ||||
|     val progressNotificationBuilder by lazy { | ||||
|         context.notificationBuilder(Notifications.CHANNEL_LIBRARY) { | ||||
|             setContentTitle(context.getString(R.string.app_name)) | ||||
|             setSmallIcon(R.drawable.ic_refresh_24dp) | ||||
|             setLargeIcon(notificationBitmap) | ||||
|             setOngoing(true) | ||||
|             setOnlyAlertOnce(true) | ||||
|             addAction(R.drawable.ic_close_24dp, context.getString(android.R.string.cancel), cancelIntent) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
|     fun showProgressNotification(manga: Manga, current: Int, total: Int) { | ||||
|         val title = if (preferences.hideNotificationContent()) { | ||||
|             context.getString(R.string.notification_check_updates) | ||||
|         } else { | ||||
|             manga.title | ||||
|         } | ||||
|  | ||||
|         context.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. | ||||
|      */ | ||||
|     fun showUpdateNotifications(updates: List<Pair<Manga, Array<Chapter>>>) { | ||||
|         if (updates.isEmpty()) { | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         NotificationManagerCompat.from(context).apply { | ||||
|             // Parent group notification | ||||
|             notify( | ||||
|                 Notifications.ID_NEW_CHAPTERS, | ||||
|                 context.notification(Notifications.CHANNEL_NEW_CHAPTERS) { | ||||
|                     setContentTitle(context.getString(R.string.notification_new_chapters)) | ||||
|                     if (updates.size == 1 && !preferences.hideNotificationContent()) { | ||||
|                         setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN)) | ||||
|                     } else { | ||||
|                         setContentText(context.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(NotificationCompat.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 context.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(NotificationCompat.GROUP_ALERT_SUMMARY) | ||||
|             priority = NotificationCompat.PRIORITY_HIGH | ||||
|  | ||||
|             // Open first chapter on tap | ||||
|             setContentIntent(NotificationReceiver.openChapterPendingActivity(context, manga, chapters.first())) | ||||
|             setAutoCancel(true) | ||||
|  | ||||
|             // Mark chapters as read action | ||||
|             addAction( | ||||
|                 R.drawable.ic_glasses_black_24dp, context.getString(R.string.action_mark_as_read), | ||||
|                 NotificationReceiver.markAsReadPendingBroadcast( | ||||
|                     context, | ||||
|                     manga, chapters, Notifications.ID_NEW_CHAPTERS | ||||
|                 ) | ||||
|             ) | ||||
|             // View chapters action | ||||
|             addAction( | ||||
|                 R.drawable.ic_book_24dp, context.getString(R.string.action_view_chapters), | ||||
|                 NotificationReceiver.openChapterPendingActivity( | ||||
|                     context, | ||||
|                     manga, Notifications.ID_NEW_CHAPTERS | ||||
|                 ) | ||||
|             ) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Cancels the progress notification. | ||||
|      */ | ||||
|     fun cancelProgressNotification() { | ||||
|         context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS) | ||||
|     } | ||||
|  | ||||
|     private fun getMangaIcon(manga: Manga): Bitmap? { | ||||
|         return try { | ||||
|             Glide.with(context) | ||||
|                 .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" | ||||
|                 context.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" | ||||
|                     context.resources.getString(R.string.notification_chapters_single, displayableChapterNumbers.first()) | ||||
|                 } else { | ||||
|                     // "Chapter 2.5 and 10 more" | ||||
|                     context.resources.getString(R.string.notification_chapters_single_and_more, displayableChapterNumbers.first(), remaining) | ||||
|                 } | ||||
|             } | ||||
|             // 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(", ") | ||||
|                     context.resources.getQuantityString(R.plurals.notification_chapters_multiple_and_more, remaining, joinedChapterNumbers, remaining) | ||||
|                 } else { | ||||
|                     // "Chapters 1, 2.5, 3" | ||||
|                     context.resources.getString(R.string.notification_chapters_multiple, displayableChapterNumbers.joinToString(", ")) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns an intent to open the main activity. | ||||
|      */ | ||||
|     private fun getNotificationIntent(): PendingIntent { | ||||
|         val intent = Intent(context, MainActivity::class.java).apply { | ||||
|             flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP | ||||
|             action = MainActivity.SHORTCUT_RECENTLY_UPDATED | ||||
|         } | ||||
|         return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         private const val NOTIF_MAX_CHAPTERS = 5 | ||||
|         private const val NOTIF_TITLE_MAX_LEN = 45 | ||||
|         private const val NOTIF_ICON_SIZE = 192 | ||||
|     } | ||||
| } | ||||
| @@ -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() | ||||
|                 notifier.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) | ||||
|                 } | ||||
|             } | ||||
|             // 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) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -37,11 +37,11 @@ class UpdaterService : Service() { | ||||
|  | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|  | ||||
|         notifier = UpdaterNotifier(this) | ||||
|         wakeLock = acquireWakeLock(javaClass.name) | ||||
|  | ||||
|         startForeground(Notifications.ID_UPDATER, notifier.onDownloadStarted().build()) | ||||
|  | ||||
|         wakeLock = acquireWakeLock(javaClass.name) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
		Reference in New Issue
	
	Block a user