mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Keep new chapters notification across updates
This commit is contained in:
		| @@ -1,10 +1,10 @@ | ||||
| package eu.kanade.tachiyomi | ||||
|  | ||||
| object Constants { | ||||
|     const val NOTIFICATION_LIBRARY_ID = 1 | ||||
|     const val NOTIFICATION_UPDATER_ID = 2 | ||||
|     const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 3 | ||||
|     const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4 | ||||
|     const val NOTIFICATION_DOWNLOAD_IMAGE_ID = 5 | ||||
|  | ||||
|     const val NOTIFICATION_LIBRARY_PROGRESS_ID = 1 | ||||
|     const val NOTIFICATION_LIBRARY_RESULT_ID = 2 | ||||
|     const val NOTIFICATION_UPDATER_ID = 3 | ||||
|     const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 4 | ||||
|     const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 5 | ||||
|     const val NOTIFICATION_DOWNLOAD_IMAGE_ID = 6 | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| 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.BitmapFactory | ||||
| import android.os.Build | ||||
| import android.os.IBinder | ||||
| import android.os.PowerManager | ||||
| import android.support.v4.app.NotificationCompat | ||||
| @@ -28,7 +30,9 @@ import eu.kanade.tachiyomi.util.* | ||||
| import rx.Observable | ||||
| import rx.Subscription | ||||
| import rx.schedulers.Schedulers | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import timber.log.Timber | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
| import java.util.* | ||||
| import java.util.concurrent.atomic.AtomicInteger | ||||
|  | ||||
| @@ -40,27 +44,12 @@ import java.util.concurrent.atomic.AtomicInteger | ||||
|  * progress of the update, and if case of an unexpected error, this service will be silently | ||||
|  * destroyed. | ||||
|  */ | ||||
| class LibraryUpdateService : Service() { | ||||
|  | ||||
|     /** | ||||
|      * Database helper. | ||||
|      */ | ||||
|     val db: DatabaseHelper by injectLazy() | ||||
|  | ||||
|     /** | ||||
|      * Source manager. | ||||
|      */ | ||||
|     val sourceManager: SourceManager by injectLazy() | ||||
|  | ||||
|     /** | ||||
|      * Preferences. | ||||
|      */ | ||||
|     val preferences: PreferencesHelper by injectLazy() | ||||
|  | ||||
|     /** | ||||
|      * Download Manager | ||||
|      */ | ||||
|     val downloadManager: DownloadManager by injectLazy() | ||||
| class LibraryUpdateService( | ||||
|         val db: DatabaseHelper = Injekt.get(), | ||||
|         val sourceManager: SourceManager = Injekt.get(), | ||||
|         val preferences: PreferencesHelper = Injekt.get(), | ||||
|         val downloadManager: DownloadManager = Injekt.get() | ||||
| ) : Service() { | ||||
|  | ||||
|     /** | ||||
|      * Wake lock that will be held until the service is destroyed. | ||||
| @@ -75,18 +64,27 @@ class LibraryUpdateService : Service() { | ||||
|     /** | ||||
|      * Pending intent of action that cancels the library update | ||||
|      */ | ||||
|     private val cancelPendingIntent by lazy {NotificationReceiver.cancelLibraryUpdatePendingBroadcast(this)} | ||||
|     private val cancelIntent by lazy { | ||||
|         NotificationReceiver.cancelLibraryUpdatePendingBroadcast(this) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Id of the library update notification. | ||||
|      * Bitmap of the app for notifications. | ||||
|      */ | ||||
|     private val notificationId: Int | ||||
|         get() = Constants.NOTIFICATION_LIBRARY_ID | ||||
|  | ||||
|     private val notificationBitmap by lazy { | ||||
|         BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Cached progress notification to avoid creating a lot. | ||||
|      */ | ||||
|     private val progressNotification by lazy { NotificationCompat.Builder(this) | ||||
|             .setSmallIcon(R.drawable.ic_refresh_white_24dp_img) | ||||
|             .setLargeIcon(notificationBitmap) | ||||
|             .setOngoing(true) | ||||
|             .addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent) | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|  | ||||
|         /** | ||||
| @@ -144,16 +142,20 @@ class LibraryUpdateService : Service() { | ||||
|      */ | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|         createAndAcquireWakeLock() | ||||
|         wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( | ||||
|                 PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock") | ||||
|         wakeLock.acquire() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Method called when the service is destroyed. It destroys the running subscription, resets | ||||
|      * the alarm and release the wake lock. | ||||
|      * Method called when the service is destroyed. It destroys subscriptions and releases the wake | ||||
|      * lock. | ||||
|      */ | ||||
|     override fun onDestroy() { | ||||
|         subscription?.unsubscribe() | ||||
|         destroyWakeLock() | ||||
|         if (wakeLock.isHeld) { | ||||
|             wakeLock.release() | ||||
|         } | ||||
|         super.onDestroy() | ||||
|     } | ||||
|  | ||||
| @@ -192,7 +194,7 @@ class LibraryUpdateService : Service() { | ||||
|                 .subscribeOn(Schedulers.io()) | ||||
|                 .subscribe({ | ||||
|                 }, { | ||||
|                     showNotification(getString(R.string.notification_update_error), "") | ||||
|                     Timber.e(it) | ||||
|                     stopSelf(startId) | ||||
|                 }, { | ||||
|                     stopSelf(startId) | ||||
| @@ -213,7 +215,7 @@ class LibraryUpdateService : Service() { | ||||
|         var listToUpdate = if (categoryId != -1) | ||||
|             db.getLibraryMangas().executeAsBlocking().filter { it.category == categoryId } | ||||
|         else { | ||||
|             val categoriesToUpdate = preferences.libraryUpdateCategories().getOrDefault().map { it.toInt() } | ||||
|             val categoriesToUpdate = preferences.libraryUpdateCategories().getOrDefault().map(String::toInt) | ||||
|             if (categoriesToUpdate.isNotEmpty()) | ||||
|                 db.getLibraryMangas().executeAsBlocking() | ||||
|                         .filter { it.category in categoriesToUpdate } | ||||
| @@ -255,7 +257,7 @@ class LibraryUpdateService : Service() { | ||||
|         // Emit each manga and update it sequentially. | ||||
|         return Observable.from(mangaToUpdate) | ||||
|                 // Notify manga that will update. | ||||
|                 .doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size, cancelPendingIntent) } | ||||
|                 .doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size) } | ||||
|                 // Update the chapters of the manga. | ||||
|                 .concatMap { manga -> | ||||
|                     updateManga(manga) | ||||
| @@ -267,11 +269,11 @@ class LibraryUpdateService : Service() { | ||||
|                             // Filter out mangas without new chapters (or failed). | ||||
|                             .filter { pair -> pair.first.isNotEmpty() } | ||||
|                             .doOnNext { | ||||
|                                 if (downloadNew) { | ||||
|                                     if (categoriesToDownload.isEmpty() || manga.category in categoriesToDownload) { | ||||
|                                         downloadChapters(manga, it.first) | ||||
|                                         hasDownloads = true | ||||
|                                     } | ||||
|                                 if (downloadNew && (categoriesToDownload.isEmpty() || | ||||
|                                         manga.category in categoriesToDownload)) { | ||||
|  | ||||
|                                     downloadChapters(manga, it.first) | ||||
|                                     hasDownloads = true | ||||
|                                 } | ||||
|                             } | ||||
|                             // Convert to the manga that contains new chapters. | ||||
| @@ -287,16 +289,17 @@ class LibraryUpdateService : Service() { | ||||
|                 } | ||||
|                 // Notify result of the overall update. | ||||
|                 .doOnCompleted { | ||||
|                     if (newUpdates.isEmpty()) { | ||||
|                         cancelNotification() | ||||
|                     } else { | ||||
|                         showResultNotification(newUpdates, failedUpdates) | ||||
|                         if (downloadNew) { | ||||
|                             if (hasDownloads) { | ||||
|                                 DownloadService.start(this) | ||||
|                             } | ||||
|                     cancelProgressNotification() | ||||
|                     if (newUpdates.isNotEmpty()) { | ||||
|                         showResultNotification(newUpdates) | ||||
|                         if (downloadNew && hasDownloads) { | ||||
|                             DownloadService.start(this) | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     if (failedUpdates.isNotEmpty()) { | ||||
|                         Timber.e("Failed updating: ${failedUpdates.map { it.title }}") | ||||
|                     } | ||||
|                 } | ||||
|     } | ||||
|  | ||||
| @@ -337,7 +340,7 @@ class LibraryUpdateService : Service() { | ||||
|         // Emit each manga and update it sequentially. | ||||
|         return Observable.from(mangaToUpdate) | ||||
|                 // Notify manga that will update. | ||||
|                 .doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size, cancelPendingIntent) } | ||||
|                 .doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size) } | ||||
|                 // Update the details of the manga. | ||||
|                 .concatMap { manga -> | ||||
|                     val source = sourceManager.get(manga.source) as? HttpSource | ||||
| @@ -352,73 +355,10 @@ class LibraryUpdateService : Service() { | ||||
|                             .onErrorReturn { manga } | ||||
|                 } | ||||
|                 .doOnCompleted { | ||||
|                     cancelNotification() | ||||
|                     cancelProgressNotification() | ||||
|                 } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the text that will be displayed in the notification when there are new chapters. | ||||
|      * | ||||
|      * @param updates a list of manga that contains new chapters. | ||||
|      * @param failedUpdates a list of manga that failed to update. | ||||
|      * @return the body of the notification to display. | ||||
|      */ | ||||
|     private fun getUpdatedMangasBody(updates: List<Manga>, failedUpdates: List<Manga>): String { | ||||
|         return buildString { | ||||
|             if (updates.isEmpty()) { | ||||
|                 append(getString(R.string.notification_no_new_chapters)) | ||||
|                 append("\n") | ||||
|             } else { | ||||
|                 append(getString(R.string.notification_new_chapters)) | ||||
|                 for (manga in updates) { | ||||
|                     append("\n") | ||||
|                     append(manga.title.chop(45)) | ||||
|                 } | ||||
|             } | ||||
|             if (!failedUpdates.isEmpty()) { | ||||
|                 append("\n\n") | ||||
|                 append(getString(R.string.notification_manga_update_failed)) | ||||
|                 for (manga in failedUpdates) { | ||||
|                     append("\n") | ||||
|                     append(manga.title.chop(45)) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Creates and acquires a wake lock until the library is updated. | ||||
|      */ | ||||
|     private fun createAndAcquireWakeLock() { | ||||
|         wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( | ||||
|                 PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock") | ||||
|         wakeLock.acquire() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Releases the wake lock if it's held. | ||||
|      */ | ||||
|     private fun destroyWakeLock() { | ||||
|         if (wakeLock.isHeld) { | ||||
|             wakeLock.release() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Shows the notification with the given title and body. | ||||
|      * | ||||
|      * @param title the title of the notification. | ||||
|      * @param body the body of the notification. | ||||
|      */ | ||||
|     private fun showNotification(title: String, body: String) { | ||||
|         notificationManager.notify(notificationId, notification { | ||||
|             setSmallIcon(R.drawable.ic_refresh_white_24dp_img) | ||||
|             setLargeIcon(notificationBitmap) | ||||
|             setContentTitle(title) | ||||
|             setContentText(body) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Shows the notification containing the currently updating manga and the progress. | ||||
|      * | ||||
| @@ -426,52 +366,60 @@ class LibraryUpdateService : Service() { | ||||
|      * @param current the current progress. | ||||
|      * @param total the total progress. | ||||
|      */ | ||||
|     private fun showProgressNotification(manga: Manga, current: Int, total: Int, cancelIntent: PendingIntent) { | ||||
|         notificationManager.notify(notificationId, notification { | ||||
|             setSmallIcon(R.drawable.ic_refresh_white_24dp_img) | ||||
|             setLargeIcon(notificationBitmap) | ||||
|             setContentTitle(manga.title) | ||||
|             setProgress(total, current, false) | ||||
|             setOngoing(true) | ||||
|             addAction(R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent) | ||||
|         }) | ||||
|     private fun showProgressNotification(manga: Manga, current: Int, total: Int) { | ||||
|         notificationManager.notify(Constants.NOTIFICATION_LIBRARY_PROGRESS_ID, progressNotification | ||||
|                 .setContentTitle(manga.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. | ||||
|      * @param failed a list of manga that failed to update. | ||||
|      */ | ||||
|     private fun showResultNotification(updates: List<Manga>, failed: List<Manga>) { | ||||
|         val title = getString(R.string.notification_update_completed) | ||||
|         val body = getUpdatedMangasBody(updates, failed) | ||||
|     private fun showResultNotification(updates: List<Manga>) { | ||||
|         val title = getString(R.string.notification_new_chapters) | ||||
|         val newUpdates = updates.map { it.title.chop(45) }.toMutableSet() | ||||
|  | ||||
|         notificationManager.notify(notificationId, notification { | ||||
|             setSmallIcon(R.drawable.ic_refresh_white_24dp_img) | ||||
|         // Append new chapters from a previous, existing notification | ||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | ||||
|             val previousNotification = notificationManager.activeNotifications | ||||
|                     .find { it.id == Constants.NOTIFICATION_LIBRARY_RESULT_ID } | ||||
|  | ||||
|             if (previousNotification != null) { | ||||
|                 val oldUpdates = previousNotification.notification.extras | ||||
|                         .getString(Notification.EXTRA_BIG_TEXT, "") | ||||
|                         .split("\n") | ||||
|  | ||||
|                 newUpdates += oldUpdates | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         notificationManager.notify(Constants.NOTIFICATION_LIBRARY_RESULT_ID, notification { | ||||
|             setSmallIcon(R.drawable.ic_book_white_24dp) | ||||
|             setLargeIcon(notificationBitmap) | ||||
|             setContentTitle(title) | ||||
|             setStyle(NotificationCompat.BigTextStyle().bigText(body)) | ||||
|             setContentIntent(notificationIntent) | ||||
|             setStyle(NotificationCompat.BigTextStyle().bigText(newUpdates.joinToString("\n"))) | ||||
|             setContentIntent(getNotificationIntent()) | ||||
|             setAutoCancel(true) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Cancels the notification. | ||||
|      * Cancels the progress notification. | ||||
|      */ | ||||
|     private fun cancelNotification() { | ||||
|         notificationManager.cancel(notificationId) | ||||
|     private fun cancelProgressNotification() { | ||||
|         notificationManager.cancel(Constants.NOTIFICATION_LIBRARY_PROGRESS_ID) | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Property that returns an intent to open the main activity. | ||||
|      * Returns an intent to open the main activity. | ||||
|      */ | ||||
|     private val notificationIntent: PendingIntent | ||||
|         get() { | ||||
|             val intent = Intent(this, MainActivity::class.java) | ||||
|             intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP | ||||
|             return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) | ||||
|         } | ||||
|     private fun getNotificationIntent(): PendingIntent { | ||||
|         val intent = Intent(this, MainActivity::class.java) | ||||
|         intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP | ||||
|         return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -45,7 +45,7 @@ class NotificationReceiver : BroadcastReceiver() { | ||||
|             ACTION_DELETE_IMAGE -> deleteImage(context, intent.getStringExtra(EXTRA_FILE_LOCATION), | ||||
|                     intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1)) | ||||
|             // Cancel library update and dismiss notification | ||||
|             ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context, Constants.NOTIFICATION_LIBRARY_ID) | ||||
|             ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context, Constants.NOTIFICATION_LIBRARY_PROGRESS_ID) | ||||
|             // Open reader activity | ||||
|             ACTION_OPEN_CHAPTER -> { | ||||
|                 openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1), | ||||
|   | ||||
| @@ -441,11 +441,11 @@ class LibraryFragment : BaseRxFragment<LibraryPresenter>(), ActionMode.Callback | ||||
|                         if (presenter.editCoverWithStream(it, manga)) { | ||||
|                             // TODO refresh cover | ||||
|                         } else { | ||||
|                             context.toast(R.string.notification_manga_update_failed) | ||||
|                             context.toast(R.string.notification_cover_update_failed) | ||||
|                         } | ||||
|                     } | ||||
|                 } catch (error: IOException) { | ||||
|                     context.toast(R.string.notification_manga_update_failed) | ||||
|                     context.toast(R.string.notification_cover_update_failed) | ||||
|                     Timber.e(error) | ||||
|                 } | ||||
|             } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user