Show notification with error log on update failures + Move notification logic out of LibraryUpdateService

This commit is contained in:
arkon 2020-05-21 02:18:51 -04:00 committed by Jay
parent e76805160c
commit 12b2da9058
9 changed files with 318 additions and 190 deletions

View File

@ -0,0 +1,242 @@
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.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import coil.Coil
import coil.request.CachePolicy
import coil.request.GetRequest
import coil.transform.CircleCropTransformation
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga
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.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 uy.kohesive.injekt.injectLazy
import java.util.ArrayList
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)
color = ContextCompat.getColor(context, R.color.colorAccent)
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 = manga.title
context.notificationManager.notify(
Notifications.ID_LIBRARY_PROGRESS,
progressNotificationBuilder
.setContentTitle(title)
.setProgress(total, current, false)
.build()
)
}
/**
* Shows notification containing update entries that failed with action to open full log.
*
* @param errors List of entry titles that failed to update.
* @param uri Uri for error log file containing all titles that failed.
*/
fun showUpdateErrorNotification(errors: List<String>, uri: Uri) {
if (errors.isEmpty()) {
return
}
context.notificationManager.notify(
Notifications.ID_LIBRARY_ERROR,
context.notificationBuilder(Notifications.CHANNEL_LIBRARY) {
setContentTitle(context.resources.getQuantityString(R.plurals.notification_update_failed, errors.size, errors.size))
setStyle(
NotificationCompat.BigTextStyle().bigText(
errors.joinToString("\n") {
it.chop(TITLE_MAX_LEN)
}
)
)
setSmallIcon(R.drawable.ic_tachi)
addAction(
R.drawable.nnf_ic_file_folder,
context.getString(R.string.view_all_errors),
NotificationReceiver.openErrorLogPendingActivity(context, uri)
)
}
.build()
)
}
/**
* Shows the notification containing the result of the update done by the service.
*
* @param updates a list of manga with new updates.
*/
suspend fun showResultNotification(updates: Map<LibraryManga, Array<Chapter>>) {
val notifications = ArrayList<Pair<Notification, Int>>()
updates.forEach {
val manga = it.key
val chapters = it.value
val chapterNames = chapters.map { chapter -> chapter.name }
notifications.add(Pair(context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachi)
try {
val request = GetRequest.Builder(context).data(manga)
.networkCachePolicy(CachePolicy.DISABLED)
.transformations(CircleCropTransformation()).size(width = ICON_SIZE, height = ICON_SIZE)
.build()
Coil.imageLoader(context)
.execute(request).drawable?.let { drawable ->
setLargeIcon((drawable as BitmapDrawable).bitmap)
}
} catch (e: Exception) { }
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
setContentTitle(manga.title)
color = ContextCompat.getColor(context, R.color.colorAccent)
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
"${chapterNames.take(MAX_CHAPTERS - 1)
.joinToString(", ")}, " + context.resources.getQuantityString(
R.plurals.notification_and_n_more,
(chapterNames.size - (MAX_CHAPTERS - 1)),
(chapterNames.size - (MAX_CHAPTERS - 1))
)
} else chapterNames.joinToString(", ")
setContentText(chaptersNames)
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
priority = NotificationCompat.PRIORITY_HIGH
setGroup(Notifications.GROUP_NEW_CHAPTERS)
setContentIntent(
NotificationReceiver.openChapterPendingActivity(
context, manga, chapters.first()
)
)
addAction(
R.drawable.ic_glasses_black_24dp, context.getString(R.string.mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(
context,
manga, chapters, Notifications.ID_NEW_CHAPTERS
)
)
addAction(
R.drawable.ic_book_white_24dp, context.getString(R.string.view_chapters),
NotificationReceiver.openChapterPendingActivity(
context,
manga, Notifications.ID_NEW_CHAPTERS
)
)
setAutoCancel(true)
}, manga.id.hashCode()))
}
NotificationManagerCompat.from(context).apply {
notify(
Notifications.ID_NEW_CHAPTERS,
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachi)
setLargeIcon(notificationBitmap)
setContentTitle(context.getString(R.string.new_chapters_found))
color = ContextCompat.getColor(context, R.color.colorAccent)
if (updates.size > 1) {
setContentText(
context.resources.getQuantityString(
R.plurals
.for_n_titles,
updates.size, updates.size
)
)
setStyle(
NotificationCompat.BigTextStyle()
.bigText(updates.keys.joinToString("\n") {
it.title.chop(45)
})
)
} else {
setContentText(updates.keys.first().title.chop(45))
}
priority = NotificationCompat.PRIORITY_HIGH
setGroup(Notifications.GROUP_NEW_CHAPTERS)
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
setGroupSummary(true)
setContentIntent(getNotificationIntent())
setAutoCancel(true)
})
notifications.forEach {
notify(it.second, it.first)
}
}
}
/**
* Cancels the progress notification.
*/
fun cancelProgressNotification() {
context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
}
/**
* 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 MAX_CHAPTERS = 5
private const val TITLE_MAX_LEN = 45
private const val ICON_SIZE = 192
}
}

View File

@ -1,25 +1,14 @@
package eu.kanade.tachiyomi.data.library package eu.kanade.tachiyomi.data.library
import android.app.Notification
import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager 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 coil.Coil import coil.Coil
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.request.GetRequest
import coil.request.LoadRequest import coil.request.LoadRequest
import coil.transform.CircleCropTransformation
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Category
@ -30,7 +19,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start 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.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
@ -39,12 +27,9 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.chop import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.notification
import eu.kanade.tachiyomi.util.system.notificationManager
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -60,7 +45,7 @@ import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.ArrayList import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -86,19 +71,7 @@ class LibraryUpdateService(
*/ */
private lateinit var wakeLock: PowerManager.WakeLock private lateinit var wakeLock: PowerManager.WakeLock
/** private lateinit var notifier: LibraryUpdateNotifier
* 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)
}
private var job: Job? = null private var job: Job? = null
@ -111,6 +84,9 @@ class LibraryUpdateService(
// List containing new updates // List containing new updates
private val newUpdates = mutableMapOf<LibraryManga, Array<Chapter>>() private val newUpdates = mutableMapOf<LibraryManga, Array<Chapter>>()
// List containing failed updates
private val failedUpdates = mutableMapOf<Manga, String?>()
val count = AtomicInteger(0) val count = AtomicInteger(0)
val jobCount = AtomicInteger(0) val jobCount = AtomicInteger(0)
@ -131,19 +107,6 @@ class LibraryUpdateService(
preferences.deleteRemovedChapters().get() != 1 preferences.deleteRemovedChapters().get() != 1
} }
/**
* Cached progress notification to avoid creating a lot.
*/
private val progressNotification by lazy {
NotificationCompat.Builder(this, Notifications.CHANNEL_LIBRARY)
.setContentTitle(getString(R.string.app_name))
.setSmallIcon(R.drawable.ic_refresh_white_24dp_img).setLargeIcon(notificationBitmap)
.setOngoing(true).setOnlyAlertOnce(true)
.setColor(ContextCompat.getColor(this, R.color.colorAccent)).addAction(
R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent
)
}
/** /**
* Defines what should be updated within a service execution. * Defines what should be updated within a service execution.
*/ */
@ -315,11 +278,12 @@ class LibraryUpdateService(
*/ */
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotification.build()) notifier = LibraryUpdateNotifier(this)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock" PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock"
) )
wakeLock.acquire(TimeUnit.MINUTES.toMillis(30)) wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
startForeground(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build())
} }
/** /**
@ -407,11 +371,11 @@ class LibraryUpdateService(
private suspend fun finishUpdates() { private suspend fun finishUpdates() {
if (jobCount.get() != 0) return if (jobCount.get() != 0) return
if (newUpdates.isNotEmpty()) { if (newUpdates.isNotEmpty()) {
showResultNotification(newUpdates) notifier.showResultNotification(newUpdates)
if (preferences.refreshCoversToo().getOrDefault() && job?.isCancelled == false) { if (preferences.refreshCoversToo().getOrDefault() && job?.isCancelled == false) {
updateDetails(newUpdates.keys.toList()) updateDetails(newUpdates.keys.toList())
cancelProgressNotification() notifier.cancelProgressNotification()
if (downloadNew && hasDownloads) { if (downloadNew && hasDownloads) {
DownloadService.start(this) DownloadService.start(this)
} }
@ -420,7 +384,15 @@ class LibraryUpdateService(
} }
newUpdates.clear() newUpdates.clear()
} }
cancelProgressNotification() if (preferences.showLibraryUpdateErrors() && failedUpdates.isNotEmpty()) {
val errorFile = writeErrorFile(failedUpdates)
notifier.showUpdateErrorNotification(
failedUpdates.map { it.key.title },
errorFile.getUriCompat(this)
)
}
failedUpdates.clear()
notifier.cancelProgressNotification()
} }
private suspend fun updateMangaInSource( private suspend fun updateMangaInSource(
@ -459,7 +431,7 @@ class LibraryUpdateService(
if (job?.isCancelled == true) { if (job?.isCancelled == true) {
return false return false
} }
showProgressNotification(manga, progress, mangaToUpdate.size) notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
val source = sourceManager.get(manga.source) as? HttpSource ?: return false val source = sourceManager.get(manga.source) as? HttpSource ?: return false
val fetchedChapters = withContext(Dispatchers.IO) { val fetchedChapters = withContext(Dispatchers.IO) {
source.fetchChapterList(manga).toBlocking().single() source.fetchChapterList(manga).toBlocking().single()
@ -489,6 +461,7 @@ class LibraryUpdateService(
return hasDownloads return hasDownloads
} catch (e: Exception) { } catch (e: Exception) {
if (e !is CancellationException) { if (e !is CancellationException) {
failedUpdates[manga] = e.message
Timber.e("Failed updating: ${manga.title}: $e") Timber.e("Failed updating: ${manga.title}: $e")
} }
return false return false
@ -496,14 +469,9 @@ class LibraryUpdateService(
} }
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) { private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
// we need to get the chapters from the db so we have chapter ids
val mangaChapters = db.getChapters(manga).executeAsBlocking()
val dbChapters = chapters.map {
mangaChapters.find { mangaChapter -> mangaChapter.url == it.url }!!
}
// We don't want to start downloading while the library is updating, because websites // We don't want to start downloading while the library is updating, because websites
// may don't like it and they could ban the user. // may don't like it and they could ban the user.
downloadManager.downloadChapters(manga, dbChapters, false) downloadManager.downloadChapters(manga, chapters, false)
} }
/** /**
@ -523,7 +491,11 @@ class LibraryUpdateService(
return@async return@async
} }
val source = sourceManager.get(manga.source) as? HttpSource ?: return@async val source = sourceManager.get(manga.source) as? HttpSource ?: return@async
showProgressNotification(manga, count.andIncrement, mangaToUpdate.size) notifier.showProgressNotification(
manga,
count.andIncrement,
mangaToUpdate.size
)
val networkManga = try { val networkManga = try {
source.fetchMangaDetailsAsync(manga) source.fetchMangaDetailsAsync(manga)
@ -550,7 +522,7 @@ class LibraryUpdateService(
} }
} }
asyncList.awaitAll() asyncList.awaitAll()
cancelProgressNotification() notifier.cancelProgressNotification()
} }
/** /**
@ -565,7 +537,7 @@ class LibraryUpdateService(
val loggedServices = trackManager.services.filter { it.isLogged } val loggedServices = trackManager.services.filter { it.isLogged }
mangaToUpdate.forEach { manga -> mangaToUpdate.forEach { manga ->
showProgressNotification(manga, count++, mangaToUpdate.size) notifier.showProgressNotification(manga, count++, mangaToUpdate.size)
val tracks = db.getTracks(manga).executeAsBlocking() val tracks = db.getTracks(manga).executeAsBlocking()
@ -581,143 +553,28 @@ class LibraryUpdateService(
} }
} }
} }
cancelProgressNotification() notifier.cancelProgressNotification()
} }
/** /**
* Shows the notification containing the currently updating manga and the progress. * Writes basic file of update errors to cache dir.
*
* @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) { private fun writeErrorFile(errors: Map<Manga, String?>): File {
notificationManager.notify( try {
Notifications.ID_LIBRARY_PROGRESS, progressNotification if (errors.isNotEmpty()) {
.setContentTitle(manga.title) val destFile = File(externalCacheDir, "tachiyomi_update_errors.txt")
.setProgress(total, current, false)
.build()
)
}
/** destFile.bufferedWriter().use { out ->
* Shows the notification containing the result of the update done by the service. errors.forEach { (manga, error) ->
* out.write("${manga.title}: $error\n")
* @param updates a list of manga with new updates.
*/
private suspend fun showResultNotification(updates: Map<LibraryManga, Array<Chapter>>) {
val notifications = ArrayList<Pair<Notification, Int>>()
updates.forEach {
val manga = it.key
val chapters = it.value
val chapterNames = chapters.map { chapter -> chapter.name }
notifications.add(Pair(notification(Notifications.CHANNEL_NEW_CHAPTERS) {
setSmallIcon(R.drawable.ic_tachi)
try {
val request = GetRequest.Builder(this@LibraryUpdateService).data(manga)
.networkCachePolicy(CachePolicy.DISABLED)
.transformations(CircleCropTransformation()).size(width = 256, height = 256)
.build()
Coil.imageLoader(this@LibraryUpdateService)
.execute(request).drawable?.let { drawable ->
setLargeIcon((drawable as BitmapDrawable).bitmap)
}
} catch (e: Exception) {
}
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
setContentTitle(manga.title)
color = ContextCompat.getColor(this@LibraryUpdateService, R.color.colorAccent)
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.mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(
this@LibraryUpdateService,
manga, chapters, Notifications.ID_NEW_CHAPTERS
)
)
addAction(
R.drawable.ic_book_white_24dp, getString(R.string.view_chapters),
NotificationReceiver.openChapterPendingActivity(
this@LibraryUpdateService,
manga, Notifications.ID_NEW_CHAPTERS
)
)
setAutoCancel(true)
}, manga.id.hashCode()))
}
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.new_chapters_found))
color = ContextCompat.getColor(applicationContext, R.color.colorAccent)
if (updates.size > 1) {
setContentText(
resources.getQuantityString(
R.plurals
.for_n_titles,
updates.size, updates.size
)
)
setStyle(
NotificationCompat.BigTextStyle()
.bigText(updates.keys.joinToString("\n") {
it.title.chop(45)
})
)
} else {
setContentText(updates.keys.first().title.chop(45))
} }
priority = NotificationCompat.PRIORITY_HIGH }
setGroup(Notifications.GROUP_NEW_CHAPTERS) return destFile
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
setGroupSummary(true)
setContentIntent(getNotificationIntent())
setAutoCancel(true)
})
notifications.forEach {
notify(it.second, it.first)
} }
} catch (e: Exception) {
// Empty
} }
} return File("")
/**
* Cancels the progress notification.
*/
private fun cancelProgressNotification() {
notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
}
/**
* Returns an intent to open the main activity.
*/
private fun getNotificationIntent(): PendingIntent {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
intent.action = MainActivity.SHORTCUT_RECENTLY_UPDATED
return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
} }
} }

View File

@ -435,6 +435,22 @@ class NotificationReceiver : BroadcastReceiver() {
) )
} }
/**
* Returns [PendingIntent] that opens the error log file in an external viewer
*
* @param context context of application
* @param uri uri of error log file
* @return [PendingIntent]
*/
internal fun openErrorLogPendingActivity(context: Context, uri: Uri): PendingIntent {
val intent = Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(uri, "text/plain")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
return PendingIntent.getActivity(context, 0, intent, 0)
}
/** /**
* Returns [PendingIntent] that opens the extensions controller, * Returns [PendingIntent] that opens the extensions controller,
* *

View File

@ -24,6 +24,7 @@ object Notifications {
*/ */
const val CHANNEL_LIBRARY = "library_channel" const val CHANNEL_LIBRARY = "library_channel"
const val ID_LIBRARY_PROGRESS = -101 const val ID_LIBRARY_PROGRESS = -101
const val ID_LIBRARY_ERROR = -102
/** /**
* Notification channel and ids used by the downloader. * Notification channel and ids used by the downloader.

View File

@ -121,8 +121,6 @@ object PreferenceKeys {
const val uniformGrid = "uniform_grid" const val uniformGrid = "uniform_grid"
const val libraryAsSingleList = "library_as_single_list"
const val lang = "app_language" const val lang = "app_language"
const val dateFormat = "app_date_format" const val dateFormat = "app_date_format"
@ -149,6 +147,8 @@ object PreferenceKeys {
const val updateOnRefresh = "update_on_refresh" const val updateOnRefresh = "update_on_refresh"
const val showLibraryUpdateErrors = "show_library_update_errors"
const val alwaysShowChapterTransition = "always_show_chapter_transition" const val alwaysShowChapterTransition = "always_show_chapter_transition"
@Deprecated("Use the preferences of the source") @Deprecated("Use the preferences of the source")

View File

@ -278,6 +278,8 @@ class PreferencesHelper(val context: Context) {
fun onlySearchPinned() = flowPrefs.getBoolean(Keys.onlySearchPinned, true) fun onlySearchPinned() = flowPrefs.getBoolean(Keys.onlySearchPinned, true)
fun showLibraryUpdateErrors() = prefs.getBoolean(Keys.showLibraryUpdateErrors, false)
// Tutorial preferences // Tutorial preferences
fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false) fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false)

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import com.chuckerteam.chucker.api.ChuckerInterceptor
import okhttp3.Cache import okhttp3.Cache
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.io.File import java.io.File
@ -17,7 +16,7 @@ class NetworkHelper(context: Context) {
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
.cookieJar(cookieManager) .cookieJar(cookieManager)
.cache(Cache(cacheDir, cacheSize)) .cache(Cache(cacheDir, cacheSize))
.addInterceptor(ChuckerInterceptor(context)) // .addInterceptor(ChuckerInterceptor(context))
.build() .build()
val cloudflareClient = client.newBuilder() val cloudflareClient = client.newBuilder()

View File

@ -159,6 +159,12 @@ class SettingsLibraryController : SettingsController() {
summaryRes = R.string.auto_refresh_covers_summary summaryRes = R.string.auto_refresh_covers_summary
defaultValue = true defaultValue = true
} }
switchPreference {
key = Keys.showLibraryUpdateErrors
titleRes = R.string.show_notification_error
defaultValue = false
}
} }
} }
} }

View File

@ -170,6 +170,10 @@
<item quantity="one">and %1$d more chapter</item> <item quantity="one">and %1$d more chapter</item>
<item quantity="other">and %1$d more chapters</item> <item quantity="other">and %1$d more chapters</item>
</plurals> </plurals>
<plurals name="notification_update_failed">
<item quantity="one">1 update failed</item>
<item quantity="other">%1$d updates failed</item>
</plurals>
<!-- Library settings --> <!-- Library settings -->
<string name="library_update_frequency">Library update frequency</string> <string name="library_update_frequency">Library update frequency</string>
@ -186,6 +190,7 @@
<string name="auto_refresh_covers">Automatically refresh covers</string> <string name="auto_refresh_covers">Automatically refresh covers</string>
<string name="auto_refresh_covers_summary">Refresh covers in library as well <string name="auto_refresh_covers_summary">Refresh covers in library as well
when updating library</string> when updating library</string>
<string name="show_notification_error">Show a notification for errors</string>
<!-- Recents --> <!-- Recents -->
<string name="recents">Recents</string> <string name="recents">Recents</string>