mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Group notifcations for Library updates (#2582)
This commit is contained in:
		@@ -52,6 +52,14 @@ interface ChapterQueries : DbProvider {
 | 
			
		||||
                    .build())
 | 
			
		||||
            .prepare()
 | 
			
		||||
 | 
			
		||||
    fun getChapter(url: String, mangaId: Long) = db.get()
 | 
			
		||||
        .`object`(Chapter::class.java)
 | 
			
		||||
        .withQuery(Query.builder()
 | 
			
		||||
            .table(ChapterTable.TABLE)
 | 
			
		||||
            .where("${ChapterTable.COL_URL} = ? AND ${ChapterTable.COL_MANGA_ID} = ?")
 | 
			
		||||
            .whereArgs(url, mangaId)
 | 
			
		||||
            .build())
 | 
			
		||||
        .prepare()
 | 
			
		||||
 | 
			
		||||
    fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,27 +30,11 @@ internal class DownloadNotifier(private val context: Context) {
 | 
			
		||||
     */
 | 
			
		||||
    private var isDownloading = false
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The size of queue on start download.
 | 
			
		||||
     */
 | 
			
		||||
    var initialQueueSize = 0
 | 
			
		||||
        set(value) {
 | 
			
		||||
            if (value != 0) {
 | 
			
		||||
                isSingleChapter = (value == 1)
 | 
			
		||||
            }
 | 
			
		||||
            field = value
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updated when error is thrown
 | 
			
		||||
     */
 | 
			
		||||
    var errorThrown = false
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updated when only single page is downloaded
 | 
			
		||||
     */
 | 
			
		||||
    var isSingleChapter = false
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updated when paused
 | 
			
		||||
     */
 | 
			
		||||
@@ -144,39 +128,6 @@ internal class DownloadNotifier(private val context: Context) {
 | 
			
		||||
 | 
			
		||||
        // Reset initial values
 | 
			
		||||
        isDownloading = false
 | 
			
		||||
        initialQueueSize = 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when chapter is downloaded.
 | 
			
		||||
     *
 | 
			
		||||
     * @param download download object containing download information.
 | 
			
		||||
     */
 | 
			
		||||
    fun onDownloadCompleted(download: Download, queue: DownloadQueue) {
 | 
			
		||||
        // Check if last download
 | 
			
		||||
        if (!queue.isEmpty()) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
        // Create notification.
 | 
			
		||||
        with(notificationBuilder) {
 | 
			
		||||
            val title = download.manga.title.chop(15)
 | 
			
		||||
            val quotedTitle = Pattern.quote(title)
 | 
			
		||||
            val chapter = download.chapter.name.replaceFirst("$quotedTitle[\\s]*[-]*[\\s]*".toRegex(RegexOption.IGNORE_CASE), "")
 | 
			
		||||
            setContentTitle("$title - $chapter".chop(30))
 | 
			
		||||
            setContentText(context.getString(R.string.update_check_notification_download_complete))
 | 
			
		||||
            setSmallIcon(android.R.drawable.stat_sys_download_done)
 | 
			
		||||
            setAutoCancel(true)
 | 
			
		||||
            clearActions()
 | 
			
		||||
            setContentIntent(NotificationReceiver.openChapterPendingBroadcast(context, download.manga, download.chapter))
 | 
			
		||||
            setProgress(0, 0, false)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Show notification.
 | 
			
		||||
        notificationBuilder.show()
 | 
			
		||||
 | 
			
		||||
        // Reset initial values
 | 
			
		||||
        isDownloading = false
 | 
			
		||||
        initialQueueSize = 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -126,8 +126,6 @@ class Downloader(
 | 
			
		||||
            if (notifier.paused) {
 | 
			
		||||
                notifier.paused = false
 | 
			
		||||
                notifier.onDownloadPaused()
 | 
			
		||||
            } else if (notifier.isSingleChapter && !notifier.errorThrown) {
 | 
			
		||||
                notifier.isSingleChapter = false
 | 
			
		||||
            } else {
 | 
			
		||||
                notifier.dismiss()
 | 
			
		||||
            }
 | 
			
		||||
@@ -229,9 +227,6 @@ class Downloader(
 | 
			
		||||
        if (chaptersToQueue.isNotEmpty()) {
 | 
			
		||||
            queue.addAll(chaptersToQueue)
 | 
			
		||||
 | 
			
		||||
            // Initialize queue size.
 | 
			
		||||
            notifier.initialQueueSize = queue.size
 | 
			
		||||
 | 
			
		||||
            if (isRunning) {
 | 
			
		||||
                // Send the list of downloads to the downloader.
 | 
			
		||||
                downloadsRelay.call(chaptersToQueue)
 | 
			
		||||
@@ -428,9 +423,6 @@ class Downloader(
 | 
			
		||||
            queue.remove(download)
 | 
			
		||||
        }
 | 
			
		||||
        if (areAllDownloadsFinished()) {
 | 
			
		||||
            if (notifier.isSingleChapter && !notifier.errorThrown) {
 | 
			
		||||
                notifier.onDownloadCompleted(download, queue)
 | 
			
		||||
            }
 | 
			
		||||
            DownloadService.stop(context)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,10 @@ 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 androidx.core.content.ContextCompat
 | 
			
		||||
import com.bumptech.glide.Glide
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
@@ -202,9 +206,9 @@ class LibraryUpdateService(
 | 
			
		||||
     * @return the start value of the command.
 | 
			
		||||
     */
 | 
			
		||||
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
 | 
			
		||||
        if (intent == null) return Service.START_NOT_STICKY
 | 
			
		||||
        if (intent == null) return START_NOT_STICKY
 | 
			
		||||
        val target = intent.getSerializableExtra(KEY_TARGET) as? Target
 | 
			
		||||
                ?: return Service.START_NOT_STICKY
 | 
			
		||||
                ?: return START_NOT_STICKY
 | 
			
		||||
 | 
			
		||||
        // Unsubscribe from any previous subscription if needed.
 | 
			
		||||
        subscription?.unsubscribe()
 | 
			
		||||
@@ -276,7 +280,7 @@ class LibraryUpdateService(
 | 
			
		||||
        // Initialize the variables holding the progress of the updates.
 | 
			
		||||
        val count = AtomicInteger(0)
 | 
			
		||||
        // List containing new updates
 | 
			
		||||
        val newUpdates = ArrayList<Manga>()
 | 
			
		||||
        val newUpdates = ArrayList<Pair<LibraryManga, Array<Chapter>>>()
 | 
			
		||||
        // list containing failed updates
 | 
			
		||||
        val failedUpdates = ArrayList<Manga>()
 | 
			
		||||
        // List containing categories that get included in downloads.
 | 
			
		||||
@@ -309,7 +313,8 @@ class LibraryUpdateService(
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                            // Convert to the manga that contains new chapters.
 | 
			
		||||
                            .map { manga }
 | 
			
		||||
                            .map { Pair(manga, (it.first.sortedByDescending { ch -> ch
 | 
			
		||||
                                .source_order }.toTypedArray())) }
 | 
			
		||||
                }
 | 
			
		||||
                // Add manga with new chapters to the list.
 | 
			
		||||
                .doOnNext { manga ->
 | 
			
		||||
@@ -331,6 +336,7 @@ class LibraryUpdateService(
 | 
			
		||||
 | 
			
		||||
                    cancelProgressNotification()
 | 
			
		||||
                }
 | 
			
		||||
                .map { manga -> manga.first }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
 | 
			
		||||
@@ -444,39 +450,76 @@ class LibraryUpdateService(
 | 
			
		||||
     *
 | 
			
		||||
     * @param updates a list of manga with new updates.
 | 
			
		||||
     */
 | 
			
		||||
    private fun showResultNotification(updates: List<Manga>) {
 | 
			
		||||
        val newUpdates = updates.map { it.title.chop(45) }.toMutableSet()
 | 
			
		||||
 | 
			
		||||
        // Append new chapters from a previous, existing notification
 | 
			
		||||
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
 | 
			
		||||
            val previousNotification = notificationManager.activeNotifications
 | 
			
		||||
                    .find { it.id == Notifications.ID_LIBRARY_RESULT }
 | 
			
		||||
 | 
			
		||||
            if (previousNotification != null) {
 | 
			
		||||
                val oldUpdates = previousNotification.notification.extras
 | 
			
		||||
                        .getString(Notification.EXTRA_BIG_TEXT)
 | 
			
		||||
 | 
			
		||||
                if (!oldUpdates.isNullOrEmpty()) {
 | 
			
		||||
                    newUpdates += oldUpdates.split("\n")
 | 
			
		||||
    private fun showResultNotification(updates: List<Pair<Manga, Array<Chapter>>>) {
 | 
			
		||||
        val notifications = ArrayList<Pair<Notification, Int>>()
 | 
			
		||||
        updates.forEach {
 | 
			
		||||
            val manga = it.first
 | 
			
		||||
            val chapters = it.second
 | 
			
		||||
            val chapterNames = chapters.map { chapter -> chapter.name }.toSet()
 | 
			
		||||
            notifications.add(Pair(notification(Notifications.CHANNEL_NEW_CHAPTERS) {
 | 
			
		||||
                setSmallIcon(R.drawable.ic_tachi)
 | 
			
		||||
                try {
 | 
			
		||||
                    val icon = Glide.with(this@LibraryUpdateService)
 | 
			
		||||
                        .asBitmap().load(manga).dontTransform().centerCrop().circleCrop()
 | 
			
		||||
                        .override(256, 256).submit().get()
 | 
			
		||||
                    setLargeIcon(icon)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
                catch (e: Exception) { }
 | 
			
		||||
                setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
 | 
			
		||||
                setContentTitle(manga.title)
 | 
			
		||||
                val chaptersNames = if (chapterNames.size > 5) {
 | 
			
		||||
                    "${chapterNames.take(4).joinToString(", ")}, " +
 | 
			
		||||
                        resources.getQuantityString(R.plurals.notification_and_n_more,
 | 
			
		||||
                            (chapterNames.size - 4), (chapterNames.size - 4))
 | 
			
		||||
                } else chapterNames.joinToString(", ")
 | 
			
		||||
                setContentText(chaptersNames)
 | 
			
		||||
                setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
 | 
			
		||||
                priority = NotificationCompat.PRIORITY_HIGH
 | 
			
		||||
                setGroup(Notifications.GROUP_NEW_CHAPTERS)
 | 
			
		||||
                setContentIntent(
 | 
			
		||||
                    NotificationReceiver.openChapterPendingActivity(
 | 
			
		||||
                        this@LibraryUpdateService, manga, chapters.first()
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
                addAction(R.drawable.ic_glasses_black_24dp, getString(R.string.action_mark_as_read),
 | 
			
		||||
                    NotificationReceiver.markAsReadPendingBroadcast(this@LibraryUpdateService,
 | 
			
		||||
                        manga, chapters, Notifications.ID_NEW_CHAPTERS))
 | 
			
		||||
                addAction(R.drawable.ic_book_white_24dp, getString(R.string.action_view_chapters),
 | 
			
		||||
                    NotificationReceiver.openChapterPendingActivity(this@LibraryUpdateService,
 | 
			
		||||
                        manga, Notifications.ID_NEW_CHAPTERS))
 | 
			
		||||
                setAutoCancel(true)
 | 
			
		||||
            }, manga.id.hashCode()))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        notificationManager.notify(Notifications.ID_LIBRARY_RESULT, notification(Notifications.CHANNEL_LIBRARY) {
 | 
			
		||||
            setSmallIcon(R.drawable.ic_book_white_24dp)
 | 
			
		||||
            setLargeIcon(notificationBitmap)
 | 
			
		||||
            setContentTitle(getString(R.string.notification_new_chapters))
 | 
			
		||||
            if (newUpdates.size > 1) {
 | 
			
		||||
                setContentText(getString(R.string.notification_new_chapters_text, newUpdates.size))
 | 
			
		||||
                setStyle(NotificationCompat.BigTextStyle().bigText(newUpdates.joinToString("\n")))
 | 
			
		||||
                setNumber(newUpdates.size)
 | 
			
		||||
            } else {
 | 
			
		||||
                setContentText(newUpdates.first())
 | 
			
		||||
        NotificationManagerCompat.from(this).apply {
 | 
			
		||||
 | 
			
		||||
            notify(Notifications.ID_NEW_CHAPTERS, notification(Notifications.CHANNEL_NEW_CHAPTERS) {
 | 
			
		||||
                setSmallIcon(R.drawable.ic_tachi)
 | 
			
		||||
                setLargeIcon(notificationBitmap)
 | 
			
		||||
                setContentTitle(getString(R.string.notification_new_chapters))
 | 
			
		||||
                if (updates.size > 1) {
 | 
			
		||||
                    setContentText(resources.getQuantityString(R.plurals
 | 
			
		||||
                        .notification_new_chapters_text,
 | 
			
		||||
                        updates.size, updates.size))
 | 
			
		||||
                    setStyle(NotificationCompat.BigTextStyle().bigText(updates.joinToString("\n") {
 | 
			
		||||
                        it.first.title.chop(45)
 | 
			
		||||
                    }))
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    setContentText(updates.first().first.title.chop(45))
 | 
			
		||||
                }
 | 
			
		||||
                priority = NotificationCompat.PRIORITY_HIGH
 | 
			
		||||
                setGroup(Notifications.GROUP_NEW_CHAPTERS)
 | 
			
		||||
                setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
 | 
			
		||||
                setGroupSummary(true)
 | 
			
		||||
                setContentIntent(getNotificationIntent())
 | 
			
		||||
                setAutoCancel(true)
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            notifications.forEach {
 | 
			
		||||
                notify(it.second, it.first)
 | 
			
		||||
            }
 | 
			
		||||
            priority = NotificationCompat.PRIORITY_HIGH
 | 
			
		||||
            setContentIntent(getNotificationIntent())
 | 
			
		||||
            setAutoCancel(true)
 | 
			
		||||
        })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import android.app.PendingIntent
 | 
			
		||||
import android.content.BroadcastReceiver
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.os.Handler
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
 | 
			
		||||
@@ -12,11 +13,17 @@ 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.library.LibraryUpdateService
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceManager
 | 
			
		||||
import eu.kanade.tachiyomi.ui.main.MainActivity
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.MangaController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
 | 
			
		||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
 | 
			
		||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.notificationManager
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.toast
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.get
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.io.File
 | 
			
		||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
 | 
			
		||||
@@ -60,6 +67,15 @@ class NotificationReceiver : BroadcastReceiver() {
 | 
			
		||||
                openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1),
 | 
			
		||||
                        intent.getLongExtra(EXTRA_CHAPTER_ID, -1))
 | 
			
		||||
            }
 | 
			
		||||
            ACTION_MARK_AS_READ -> {
 | 
			
		||||
                val notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)
 | 
			
		||||
                if (notificationId > -1) dismissNotification(
 | 
			
		||||
                    context, notificationId, intent.getIntExtra(EXTRA_GROUP_ID, 0)
 | 
			
		||||
                )
 | 
			
		||||
                val urls = intent.getStringArrayExtra(EXTRA_CHAPTER_URL) ?: return
 | 
			
		||||
                val mangaId = intent.getLongExtra(EXTRA_MANGA_ID, -1)
 | 
			
		||||
                markAsRead(urls, mangaId)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -104,7 +120,6 @@ class NotificationReceiver : BroadcastReceiver() {
 | 
			
		||||
        val db = DatabaseHelper(context)
 | 
			
		||||
        val manga = db.getManga(mangaId).executeAsBlocking()
 | 
			
		||||
        val chapter = db.getChapter(chapterId).executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
        if (manga != null && chapter != null) {
 | 
			
		||||
            val intent = ReaderActivity.newIntent(context, manga, chapter).apply {
 | 
			
		||||
                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
 | 
			
		||||
@@ -143,6 +158,28 @@ class NotificationReceiver : BroadcastReceiver() {
 | 
			
		||||
        Handler().post { dismissNotification(context, notificationId) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method called when user wants to mark as read
 | 
			
		||||
     *
 | 
			
		||||
     * @param context context of application
 | 
			
		||||
     * @param notificationId id of notification
 | 
			
		||||
     */
 | 
			
		||||
    private fun markAsRead(chapterUrls: Array<String>, mangaId: Long) {
 | 
			
		||||
        val db: DatabaseHelper = Injekt.get()
 | 
			
		||||
        chapterUrls.forEach {
 | 
			
		||||
            val chapter = db.getChapter(it, mangaId).executeAsBlocking() ?: return
 | 
			
		||||
            chapter.read = true
 | 
			
		||||
            db.updateChapterProgress(chapter).executeAsBlocking()
 | 
			
		||||
            val preferences: PreferencesHelper = Injekt.get()
 | 
			
		||||
            if (preferences.removeAfterMarkedAsRead()) {
 | 
			
		||||
                val manga = db.getManga(mangaId).executeAsBlocking() ?: return
 | 
			
		||||
                val sourceManager: SourceManager = Injekt.get()
 | 
			
		||||
                val source = sourceManager.get(manga.source) ?: return
 | 
			
		||||
                downloadManager.deleteChapters(listOf(chapter), manga, source)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        private const val NAME = "NotificationReceiver"
 | 
			
		||||
 | 
			
		||||
@@ -155,6 +192,9 @@ class NotificationReceiver : BroadcastReceiver() {
 | 
			
		||||
        // Called to cancel library update.
 | 
			
		||||
        private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"
 | 
			
		||||
 | 
			
		||||
        // Called to mark as read
 | 
			
		||||
        private const val ACTION_MARK_AS_READ = "$ID.$NAME.MARK_AS_READ"
 | 
			
		||||
 | 
			
		||||
        // Called to open chapter
 | 
			
		||||
        private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER"
 | 
			
		||||
 | 
			
		||||
@@ -179,12 +219,18 @@ class NotificationReceiver : BroadcastReceiver() {
 | 
			
		||||
        // Value containing notification id.
 | 
			
		||||
        private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID"
 | 
			
		||||
 | 
			
		||||
        // Value containing group id.
 | 
			
		||||
        private const val EXTRA_GROUP_ID = "$ID.$NAME.EXTRA_GROUP_ID"
 | 
			
		||||
 | 
			
		||||
        // Value containing manga id.
 | 
			
		||||
        private const val EXTRA_MANGA_ID = "$ID.$NAME.EXTRA_MANGA_ID"
 | 
			
		||||
 | 
			
		||||
        // Value containing chapter id.
 | 
			
		||||
        private const val EXTRA_CHAPTER_ID = "$ID.$NAME.EXTRA_CHAPTER_ID"
 | 
			
		||||
 | 
			
		||||
        // Value containing chapter url.
 | 
			
		||||
        private const val EXTRA_CHAPTER_URL = "$ID.$NAME.EXTRA_CHAPTER_URL"
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns a [PendingIntent] that resumes the download of a chapter
 | 
			
		||||
         *
 | 
			
		||||
@@ -246,6 +292,32 @@ class NotificationReceiver : BroadcastReceiver() {
 | 
			
		||||
            return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns [PendingIntent] that starts a service which dismissed the notification
 | 
			
		||||
         *
 | 
			
		||||
         * @param context context of application
 | 
			
		||||
         * @param notificationId id of notification
 | 
			
		||||
         * @return [PendingIntent]
 | 
			
		||||
         */
 | 
			
		||||
        internal fun dismissNotification(context: Context, notificationId: Int, groupId: Int? =
 | 
			
		||||
            null) {
 | 
			
		||||
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 | 
			
		||||
                val groupKey = context.notificationManager.activeNotifications.find {
 | 
			
		||||
                    it.id == notificationId
 | 
			
		||||
                }?.groupKey
 | 
			
		||||
                if (groupId != null && groupId != 0 && groupKey != null && groupKey.isNotEmpty()) {
 | 
			
		||||
                    val notifications = context.notificationManager.activeNotifications.filter {
 | 
			
		||||
                        it.groupKey == groupKey
 | 
			
		||||
                    }
 | 
			
		||||
                    if (notifications.size == 2) {
 | 
			
		||||
                        context.notificationManager.cancel(groupId)
 | 
			
		||||
                        return
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            context.notificationManager.cancel(notificationId)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns [PendingIntent] that starts a service which cancels the notification and starts a share activity
 | 
			
		||||
         *
 | 
			
		||||
@@ -281,19 +353,55 @@ class NotificationReceiver : BroadcastReceiver() {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns [PendingIntent] that start a reader activity containing chapter.
 | 
			
		||||
         * Returns [PendingIntent] that starts a reader activity containing chapter.
 | 
			
		||||
         *
 | 
			
		||||
         * @param context context of application
 | 
			
		||||
         * @param manga manga of chapter
 | 
			
		||||
         * @param chapter chapter that needs to be opened
 | 
			
		||||
         */
 | 
			
		||||
        internal fun openChapterPendingBroadcast(context: Context, manga: Manga, chapter: Chapter): PendingIntent {
 | 
			
		||||
            val intent = Intent(context, NotificationReceiver::class.java).apply {
 | 
			
		||||
                action = ACTION_OPEN_CHAPTER
 | 
			
		||||
        internal fun openChapterPendingActivity(context: Context, manga: Manga, chapter:
 | 
			
		||||
        Chapter): PendingIntent {
 | 
			
		||||
            val newIntent = ReaderActivity.newIntent(context, manga, chapter)
 | 
			
		||||
            return PendingIntent.getActivity(context, manga.id.hashCode(), newIntent, PendingIntent
 | 
			
		||||
                .FLAG_UPDATE_CURRENT)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns [PendingIntent] that opens the manga info controller.
 | 
			
		||||
         *
 | 
			
		||||
         * @param context context of application
 | 
			
		||||
         * @param manga manga of chapter
 | 
			
		||||
         */
 | 
			
		||||
        internal fun openChapterPendingActivity(context: Context, manga: Manga, groupId: Int):
 | 
			
		||||
            PendingIntent {
 | 
			
		||||
            val newIntent =
 | 
			
		||||
                Intent(context, MainActivity::class.java).setAction(MainActivity.SHORTCUT_MANGA)
 | 
			
		||||
                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
 | 
			
		||||
                    .putExtra(MangaController.MANGA_EXTRA, manga.id)
 | 
			
		||||
                    .putExtra("notificationId", manga.id.hashCode())
 | 
			
		||||
                    .putExtra("groupId", groupId)
 | 
			
		||||
            return PendingIntent.getActivity(
 | 
			
		||||
                context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns [PendingIntent] that marks a chapter as read and deletes it if preferred
 | 
			
		||||
         *
 | 
			
		||||
         * @param context context of application
 | 
			
		||||
         * @param manga manga of chapter
 | 
			
		||||
         */
 | 
			
		||||
        internal fun markAsReadPendingBroadcast(context: Context, manga: Manga, chapters:
 | 
			
		||||
            Array<Chapter>, groupId: Int):
 | 
			
		||||
            PendingIntent {
 | 
			
		||||
            val newIntent = Intent(context, NotificationReceiver::class.java).apply {
 | 
			
		||||
                action = ACTION_MARK_AS_READ
 | 
			
		||||
                putExtra(EXTRA_CHAPTER_URL, chapters.map { it.url }.toTypedArray())
 | 
			
		||||
                putExtra(EXTRA_MANGA_ID, manga.id)
 | 
			
		||||
                putExtra(EXTRA_CHAPTER_ID, chapter.id)
 | 
			
		||||
                putExtra(EXTRA_NOTIFICATION_ID, manga.id.hashCode())
 | 
			
		||||
                putExtra(EXTRA_GROUP_ID, groupId)
 | 
			
		||||
            }
 | 
			
		||||
            return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
 | 
			
		||||
            return PendingIntent.getBroadcast(context, manga.id.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
 
 | 
			
		||||
@@ -23,15 +23,21 @@ object Notifications {
 | 
			
		||||
     * Notification channel and ids used by the library updater.
 | 
			
		||||
     */
 | 
			
		||||
    const val CHANNEL_LIBRARY = "library_channel"
 | 
			
		||||
    const val ID_LIBRARY_PROGRESS = 101
 | 
			
		||||
    const val ID_LIBRARY_RESULT = 102
 | 
			
		||||
    const val ID_LIBRARY_PROGRESS = -101
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notification channel and ids used by the downloader.
 | 
			
		||||
     */
 | 
			
		||||
    const val CHANNEL_DOWNLOADER = "downloader_channel"
 | 
			
		||||
    const val ID_DOWNLOAD_CHAPTER = 201
 | 
			
		||||
    const val ID_DOWNLOAD_CHAPTER_ERROR = 202
 | 
			
		||||
    const val ID_DOWNLOAD_CHAPTER = -201
 | 
			
		||||
    const val ID_DOWNLOAD_CHAPTER_ERROR = -202
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notification channel and ids used by the library updater.
 | 
			
		||||
     */
 | 
			
		||||
    const val CHANNEL_NEW_CHAPTERS = "new_chapters_channel"
 | 
			
		||||
    const val ID_NEW_CHAPTERS = -301
 | 
			
		||||
    const val GROUP_NEW_CHAPTERS = "eu.kanade.tachiyomi.NEW_CHAPTERS"
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the notification channels introduced in Android Oreo.
 | 
			
		||||
@@ -45,9 +51,15 @@ object Notifications {
 | 
			
		||||
                NotificationChannel(CHANNEL_COMMON, context.getString(R.string.channel_common),
 | 
			
		||||
                        NotificationManager.IMPORTANCE_LOW),
 | 
			
		||||
                NotificationChannel(CHANNEL_LIBRARY, context.getString(R.string.channel_library),
 | 
			
		||||
                        NotificationManager.IMPORTANCE_LOW),
 | 
			
		||||
                        NotificationManager.IMPORTANCE_LOW).apply {
 | 
			
		||||
                    setShowBadge(false)
 | 
			
		||||
                },
 | 
			
		||||
                NotificationChannel(CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader),
 | 
			
		||||
                        NotificationManager.IMPORTANCE_LOW)
 | 
			
		||||
                        NotificationManager.IMPORTANCE_LOW).apply {
 | 
			
		||||
                    setShowBadge(false)
 | 
			
		||||
                },
 | 
			
		||||
                NotificationChannel(CHANNEL_NEW_CHAPTERS, context.getString(R.string.channel_new_chapters),
 | 
			
		||||
                        NotificationManager.IMPORTANCE_DEFAULT)
 | 
			
		||||
        )
 | 
			
		||||
        context.notificationManager.createNotificationChannels(channels)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import androidx.drawerlayout.widget.DrawerLayout
 | 
			
		||||
import com.bluelinelabs.conductor.*
 | 
			
		||||
import eu.kanade.tachiyomi.Migrations
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.*
 | 
			
		||||
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
 | 
			
		||||
@@ -136,6 +137,10 @@ class MainActivity : BaseActivity() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun handleIntentAction(intent: Intent): Boolean {
 | 
			
		||||
        val notificationId = intent.getIntExtra("notificationId", -1)
 | 
			
		||||
        if (notificationId > -1) NotificationReceiver.dismissNotification(
 | 
			
		||||
            applicationContext, notificationId, intent.getIntExtra("groupId", 0)
 | 
			
		||||
        )
 | 
			
		||||
        when (intent.action) {
 | 
			
		||||
            SHORTCUT_LIBRARY -> setSelectedDrawerItem(R.id.nav_drawer_library)
 | 
			
		||||
            SHORTCUT_RECENTLY_UPDATED -> setSelectedDrawerItem(R.id.nav_drawer_recent_updates)
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,8 @@ import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
 | 
			
		||||
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.notification.NotificationReceiver
 | 
			
		||||
import eu.kanade.tachiyomi.data.notification.Notifications
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
 | 
			
		||||
@@ -104,10 +106,14 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
 | 
			
		||||
        const val VERTICAL = 3
 | 
			
		||||
        const val WEBTOON = 4
 | 
			
		||||
 | 
			
		||||
        fun newIntent(context: Context, manga: Manga, chapter: Chapter): Intent {
 | 
			
		||||
        fun newIntent(context: Context, manga: Manga, chapter: Chapter):
 | 
			
		||||
            Intent {
 | 
			
		||||
            val intent = Intent(context, ReaderActivity::class.java)
 | 
			
		||||
            intent.putExtra("manga", manga.id)
 | 
			
		||||
            intent.putExtra("chapter", chapter.id)
 | 
			
		||||
            // chapters just added from library updates don't have an id yet
 | 
			
		||||
            intent.putExtra("chapterUrl", chapter.url)
 | 
			
		||||
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
 | 
			
		||||
            return intent
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -126,13 +132,14 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
 | 
			
		||||
        if (presenter.needsInit()) {
 | 
			
		||||
            val manga = intent.extras!!.getLong("manga", -1)
 | 
			
		||||
            val chapter = intent.extras!!.getLong("chapter", -1)
 | 
			
		||||
 | 
			
		||||
            if (manga == -1L || chapter == -1L) {
 | 
			
		||||
            val chapterUrl = intent.extras!!.getString("chapterUrl", "")
 | 
			
		||||
            if (manga == -1L || chapterUrl == "" && chapter == -1L) {
 | 
			
		||||
                finish()
 | 
			
		||||
                return
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            presenter.init(manga, chapter)
 | 
			
		||||
            NotificationReceiver.dismissNotification(this, manga.hashCode(), Notifications.ID_NEW_CHAPTERS)
 | 
			
		||||
            if (chapter > -1) presenter.init(manga, chapter)
 | 
			
		||||
            else presenter.init(manga, chapterUrl)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (savedState != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -185,6 +185,19 @@ class ReaderPresenter(
 | 
			
		||||
                }, ReaderActivity::setInitialChapterError)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes this presenter with the given [mangaId] and [chapterUrl]. This method will
 | 
			
		||||
     * fetch the manga from the database and initialize the initial chapter.
 | 
			
		||||
     */
 | 
			
		||||
    fun init(mangaId: Long, chapterUrl: String) {
 | 
			
		||||
        if (!needsInit()) return
 | 
			
		||||
        val context = Injekt.get<Application>()
 | 
			
		||||
        val db = DatabaseHelper(context)
 | 
			
		||||
        val chapterId = db.getChapter(chapterUrl, mangaId).executeAsBlocking()?.id
 | 
			
		||||
        if (chapterId != null)
 | 
			
		||||
            init(mangaId, chapterId)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes this presenter with the given [manga] and [initialChapterId]. This method will
 | 
			
		||||
     * set the chapter loader, view subscriptions and trigger an initial load.
 | 
			
		||||
 
 | 
			
		||||
@@ -13,12 +13,14 @@ import eu.davidea.flexibleadapter.items.IFlexible
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.model.Download
 | 
			
		||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
 | 
			
		||||
import eu.kanade.tachiyomi.data.notification.Notifications
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.popControllerWithTag
 | 
			
		||||
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
 | 
			
		||||
import eu.kanade.tachiyomi.ui.manga.MangaController
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.notificationManager
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.toast
 | 
			
		||||
import kotlinx.android.synthetic.main.recent_chapters_controller.empty_view
 | 
			
		||||
import kotlinx.android.synthetic.main.recent_chapters_controller.recycler
 | 
			
		||||
@@ -68,7 +70,7 @@ class RecentChaptersController : NucleusController<RecentChaptersPresenter>(),
 | 
			
		||||
     */
 | 
			
		||||
    override fun onViewCreated(view: View) {
 | 
			
		||||
        super.onViewCreated(view)
 | 
			
		||||
 | 
			
		||||
        view.context.notificationManager.cancel(Notifications.ID_NEW_CHAPTERS)
 | 
			
		||||
        // Init RecyclerView and adapter
 | 
			
		||||
        val layoutManager = LinearLayoutManager(view.context)
 | 
			
		||||
        recycler.layoutManager = layoutManager
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user