mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 08:08:55 +01:00 
			
		
		
		
	Reword library update restrictions setting and surface skipped entries in error notification/log
This commit is contained in:
		@@ -5,7 +5,7 @@ import androidx.core.content.edit
 | 
			
		||||
import androidx.preference.PreferenceManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_ONGOING
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
@@ -244,7 +244,7 @@ object Migrations {
 | 
			
		||||
            if (oldVersion < 72) {
 | 
			
		||||
                val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true)
 | 
			
		||||
                if (!oldUpdateOngoingOnly) {
 | 
			
		||||
                    preferences.libraryUpdateMangaRestriction() -= MANGA_ONGOING
 | 
			
		||||
                    preferences.libraryUpdateMangaRestriction() -= MANGA_NON_COMPLETED
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if (oldVersion < 75) {
 | 
			
		||||
 
 | 
			
		||||
@@ -92,18 +92,19 @@ class LibraryUpdateNotifier(private val context: Context) {
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows notification containing update entries that failed with action to open full log.
 | 
			
		||||
     *
 | 
			
		||||
     * @param errors List of entry titles that failed to update.
 | 
			
		||||
     * @param skipped Number of entries that were skipped during the update.
 | 
			
		||||
     * @param failed Number of entries 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()) {
 | 
			
		||||
    fun showUpdateErrorNotification(skipped: Int, failed: Int, uri: Uri) {
 | 
			
		||||
        if (skipped == 0 && failed == 0) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        context.notificationManager.notify(
 | 
			
		||||
            Notifications.ID_LIBRARY_ERROR,
 | 
			
		||||
            context.notificationBuilder(Notifications.CHANNEL_LIBRARY_ERROR) {
 | 
			
		||||
                setContentTitle(context.resources.getQuantityString(R.plurals.notification_update_error, errors.size, errors.size))
 | 
			
		||||
                setContentTitle(context.resources.getString(R.string.notification_update_skipped_error, skipped, failed))
 | 
			
		||||
                setContentText(context.getString(R.string.action_show_errors))
 | 
			
		||||
                setSmallIcon(R.drawable.ic_tachi)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,9 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.download.DownloadService
 | 
			
		||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
 | 
			
		||||
import eu.kanade.tachiyomi.data.notification.Notifications
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_FULLY_READ
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_ONGOING
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_STARTED
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
@@ -141,7 +141,7 @@ class LibraryUpdateService(
 | 
			
		||||
 | 
			
		||||
                true
 | 
			
		||||
            } else {
 | 
			
		||||
                instance?.addMangaToQueue(category?.id ?: -1, target)
 | 
			
		||||
                instance?.addMangaToQueue(category?.id ?: -1)
 | 
			
		||||
                false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -213,7 +213,7 @@ class LibraryUpdateService(
 | 
			
		||||
 | 
			
		||||
        // Update favorite manga
 | 
			
		||||
        val categoryId = intent.getIntExtra(KEY_CATEGORY, -1)
 | 
			
		||||
        addMangaToQueue(categoryId, target)
 | 
			
		||||
        addMangaToQueue(categoryId)
 | 
			
		||||
 | 
			
		||||
        // Destroy service when completed or in case of an error.
 | 
			
		||||
        val handler = CoroutineExceptionHandler { _, exception ->
 | 
			
		||||
@@ -238,10 +238,10 @@ class LibraryUpdateService(
 | 
			
		||||
     * @param category the ID of the category to update, or -1 if no category specified.
 | 
			
		||||
     * @param target the target to update.
 | 
			
		||||
     */
 | 
			
		||||
    fun addMangaToQueue(categoryId: Int, target: Target) {
 | 
			
		||||
    fun addMangaToQueue(categoryId: Int) {
 | 
			
		||||
        val libraryManga = db.getLibraryMangas().executeAsBlocking()
 | 
			
		||||
 | 
			
		||||
        var listToUpdate = if (categoryId != -1) {
 | 
			
		||||
        val listToUpdate = if (categoryId != -1) {
 | 
			
		||||
            libraryManga.filter { it.category == categoryId }
 | 
			
		||||
        } else {
 | 
			
		||||
            val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt)
 | 
			
		||||
@@ -261,23 +261,6 @@ class LibraryUpdateService(
 | 
			
		||||
            listToInclude.minus(listToExclude)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (target == Target.CHAPTERS) {
 | 
			
		||||
            val restrictions = preferences.libraryUpdateMangaRestriction().get()
 | 
			
		||||
            if (MANGA_ONGOING in restrictions) {
 | 
			
		||||
                listToUpdate = listToUpdate.filterNot { it.status == SManga.COMPLETED }
 | 
			
		||||
            }
 | 
			
		||||
            if (MANGA_FULLY_READ in restrictions) {
 | 
			
		||||
                listToUpdate = listToUpdate.filter { it.unreadCount == 0 }
 | 
			
		||||
            }
 | 
			
		||||
            if (MANGA_STARTED in restrictions) {
 | 
			
		||||
                listToUpdate = listToUpdate.filter { manga ->
 | 
			
		||||
                    // If the manga has 0 chapters you can't actually start reading it
 | 
			
		||||
                    if (manga.totalChapters == 0) true
 | 
			
		||||
                    else manga.hasStarted
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mangaToUpdate = listToUpdate
 | 
			
		||||
            .distinctBy { it.id }
 | 
			
		||||
            .sortedBy { it.title }
 | 
			
		||||
@@ -306,10 +289,12 @@ class LibraryUpdateService(
 | 
			
		||||
        val progressCount = AtomicInteger(0)
 | 
			
		||||
        val currentlyUpdatingManga = CopyOnWriteArrayList<LibraryManga>()
 | 
			
		||||
        val newUpdates = CopyOnWriteArrayList<Pair<LibraryManga, Array<Chapter>>>()
 | 
			
		||||
        val skippedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
 | 
			
		||||
        val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
 | 
			
		||||
        val hasDownloads = AtomicBoolean(false)
 | 
			
		||||
        val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
 | 
			
		||||
        val currentUnreadUpdatesCount = preferences.unreadUpdatesCount().get()
 | 
			
		||||
        val restrictions = preferences.libraryUpdateMangaRestriction().get()
 | 
			
		||||
 | 
			
		||||
        withIOContext {
 | 
			
		||||
            mangaToUpdate.groupBy { it.source }
 | 
			
		||||
@@ -328,6 +313,16 @@ class LibraryUpdateService(
 | 
			
		||||
                                    manga,
 | 
			
		||||
                                ) { manga ->
 | 
			
		||||
                                    try {
 | 
			
		||||
                                        if (MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED) {
 | 
			
		||||
                                            throw SkipUpdateException(getString(R.string.skipped_reason_completed))
 | 
			
		||||
                                        }
 | 
			
		||||
                                        if (MANGA_HAS_UNREAD in restrictions && manga.unreadCount != 0) {
 | 
			
		||||
                                            throw SkipUpdateException(getString(R.string.skipped_reason_not_caught_up))
 | 
			
		||||
                                        }
 | 
			
		||||
                                        if (MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasStarted) {
 | 
			
		||||
                                            throw SkipUpdateException(getString(R.string.skipped_reason_not_started))
 | 
			
		||||
                                        }
 | 
			
		||||
 | 
			
		||||
                                        val (newChapters, _) = updateManga(manga)
 | 
			
		||||
 | 
			
		||||
                                        if (newChapters.isNotEmpty()) {
 | 
			
		||||
@@ -342,6 +337,8 @@ class LibraryUpdateService(
 | 
			
		||||
                                                    .toTypedArray()
 | 
			
		||||
                                            )
 | 
			
		||||
                                        }
 | 
			
		||||
                                    } catch (e: SkipUpdateException) {
 | 
			
		||||
                                        skippedUpdates.add(manga to e.message)
 | 
			
		||||
                                    } catch (e: Throwable) {
 | 
			
		||||
                                        val errorMessage = when (e) {
 | 
			
		||||
                                            is NoChaptersException -> {
 | 
			
		||||
@@ -380,11 +377,12 @@ class LibraryUpdateService(
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (failedUpdates.isNotEmpty()) {
 | 
			
		||||
            val errorFile = writeErrorFile(failedUpdates)
 | 
			
		||||
        if (skippedUpdates.isNotEmpty() || failedUpdates.isNotEmpty()) {
 | 
			
		||||
            val errorFile = writeErrorFile(skippedUpdates + failedUpdates)
 | 
			
		||||
            notifier.showUpdateErrorNotification(
 | 
			
		||||
                failedUpdates.map { it.first.title },
 | 
			
		||||
                errorFile.getUriCompat(this)
 | 
			
		||||
                skippedUpdates.size,
 | 
			
		||||
                failedUpdates.size,
 | 
			
		||||
                errorFile.getUriCompat(this),
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -587,3 +585,5 @@ class LibraryUpdateService(
 | 
			
		||||
 | 
			
		||||
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
 | 
			
		||||
private const val ERROR_LOG_HELP_URL = "https://tachiyomi.org/help/guides/troubleshooting"
 | 
			
		||||
 | 
			
		||||
private class SkipUpdateException(override val message: String) : RuntimeException()
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,9 @@ import eu.kanade.tachiyomi.R
 | 
			
		||||
const val DEVICE_ONLY_ON_WIFI = "wifi"
 | 
			
		||||
const val DEVICE_CHARGING = "ac"
 | 
			
		||||
 | 
			
		||||
const val MANGA_ONGOING = "manga_ongoing"
 | 
			
		||||
const val MANGA_FULLY_READ = "manga_fully_read"
 | 
			
		||||
const val MANGA_STARTED = "manga_started"
 | 
			
		||||
const val MANGA_NON_COMPLETED = "manga_ongoing"
 | 
			
		||||
const val MANGA_HAS_UNREAD = "manga_fully_read"
 | 
			
		||||
const val MANGA_NON_READ = "manga_started"
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class stores the values for the preferences in the application.
 | 
			
		||||
 
 | 
			
		||||
@@ -224,7 +224,7 @@ class PreferencesHelper(val context: Context) {
 | 
			
		||||
    fun libraryUpdateInterval() = flowPrefs.getInt("pref_library_update_interval_key", 24)
 | 
			
		||||
 | 
			
		||||
    fun libraryUpdateDeviceRestriction() = flowPrefs.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI))
 | 
			
		||||
    fun libraryUpdateMangaRestriction() = flowPrefs.getStringSet("library_update_manga_restriction", setOf(MANGA_FULLY_READ, MANGA_ONGOING, MANGA_STARTED))
 | 
			
		||||
    fun libraryUpdateMangaRestriction() = flowPrefs.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ))
 | 
			
		||||
 | 
			
		||||
    fun showUpdatesNavBadge() = flowPrefs.getBoolean("library_update_show_tab_badge", false)
 | 
			
		||||
    fun unreadUpdatesCount() = flowPrefs.getInt("library_unread_updates_count", 0)
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,9 @@ import eu.kanade.tachiyomi.data.database.models.Category
 | 
			
		||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_FULLY_READ
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_ONGOING
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_STARTED
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.track.TrackManager
 | 
			
		||||
import eu.kanade.tachiyomi.databinding.PrefLibraryColumnsBinding
 | 
			
		||||
@@ -196,16 +196,16 @@ class SettingsLibraryController : SettingsController() {
 | 
			
		||||
            multiSelectListPreference {
 | 
			
		||||
                bindTo(preferences.libraryUpdateMangaRestriction())
 | 
			
		||||
                titleRes = R.string.pref_library_update_manga_restriction
 | 
			
		||||
                entriesRes = arrayOf(R.string.pref_update_only_completely_read, R.string.pref_update_only_non_completed, R.string.pref_update_only_started)
 | 
			
		||||
                entryValues = arrayOf(MANGA_FULLY_READ, MANGA_ONGOING, MANGA_STARTED)
 | 
			
		||||
                entriesRes = arrayOf(R.string.pref_update_only_completely_read, R.string.pref_update_only_started, R.string.pref_update_only_non_completed)
 | 
			
		||||
                entryValues = arrayOf(MANGA_HAS_UNREAD, MANGA_NON_READ, MANGA_NON_COMPLETED)
 | 
			
		||||
 | 
			
		||||
                fun updateSummary() {
 | 
			
		||||
                    val restrictions = preferences.libraryUpdateMangaRestriction().get().sorted()
 | 
			
		||||
                        .map {
 | 
			
		||||
                            when (it) {
 | 
			
		||||
                                MANGA_ONGOING -> context.getString(R.string.pref_update_only_non_completed)
 | 
			
		||||
                                MANGA_FULLY_READ -> context.getString(R.string.pref_update_only_completely_read)
 | 
			
		||||
                                MANGA_STARTED -> context.getString(R.string.pref_update_only_started)
 | 
			
		||||
                                MANGA_NON_READ -> context.getString(R.string.pref_update_only_started)
 | 
			
		||||
                                MANGA_HAS_UNREAD -> context.getString(R.string.pref_update_only_completely_read)
 | 
			
		||||
                                MANGA_NON_COMPLETED -> context.getString(R.string.pref_update_only_non_completed)
 | 
			
		||||
                                else -> it
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
@@ -215,7 +215,7 @@ class SettingsLibraryController : SettingsController() {
 | 
			
		||||
                        restrictions.joinToString()
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    summary = context.getString(R.string.only_update_restrictions, restrictionsText)
 | 
			
		||||
                    summary = restrictionsText
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                preferences.libraryUpdateMangaRestriction().asFlow()
 | 
			
		||||
@@ -234,23 +234,24 @@ class SettingsLibraryController : SettingsController() {
 | 
			
		||||
                    val includedCategories = preferences.libraryUpdateCategories().get()
 | 
			
		||||
                        .mapNotNull { id -> categories.find { it.id == id.toInt() } }
 | 
			
		||||
                        .sortedBy { it.order }
 | 
			
		||||
 | 
			
		||||
                    val excludedCategories = preferences.libraryUpdateCategoriesExclude().get()
 | 
			
		||||
                        .mapNotNull { id -> categories.find { it.id == id.toInt() } }
 | 
			
		||||
                        .sortedBy { it.order }
 | 
			
		||||
 | 
			
		||||
                    val includedItemsText = if (includedCategories.isEmpty()) {
 | 
			
		||||
                        context.getString(R.string.none)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (includedCategories.size == categories.size) context.getString(R.string.all)
 | 
			
		||||
                        else includedCategories.joinToString { it.name }
 | 
			
		||||
                    }
 | 
			
		||||
                    val allExcluded = excludedCategories.size == categories.size
 | 
			
		||||
 | 
			
		||||
                    val excludedItemsText = if (excludedCategories.isEmpty()) {
 | 
			
		||||
                        context.getString(R.string.none)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        if (excludedCategories.size == categories.size) context.getString(R.string.all)
 | 
			
		||||
                        else excludedCategories.joinToString { it.name }
 | 
			
		||||
                    val includedItemsText = when {
 | 
			
		||||
                        // Some selected, but not all
 | 
			
		||||
                        includedCategories.isNotEmpty() && includedCategories.size != categories.size -> includedCategories.joinToString { it.name }
 | 
			
		||||
                        // All explicitly selected
 | 
			
		||||
                        includedCategories.size == categories.size -> context.getString(R.string.all)
 | 
			
		||||
                        allExcluded -> context.getString(R.string.none)
 | 
			
		||||
                        else -> context.getString(R.string.all)
 | 
			
		||||
                    }
 | 
			
		||||
                    val excludedItemsText = when {
 | 
			
		||||
                        excludedCategories.isEmpty() -> context.getString(R.string.none)
 | 
			
		||||
                        allExcluded -> context.getString(R.string.all)
 | 
			
		||||
                        else -> excludedCategories.joinToString { it.name }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    summary = buildSpannedString {
 | 
			
		||||
@@ -340,8 +341,10 @@ class SettingsLibraryController : SettingsController() {
 | 
			
		||||
            var selected = categories
 | 
			
		||||
                .map {
 | 
			
		||||
                    when (it.id.toString()) {
 | 
			
		||||
                        in preferences.libraryUpdateCategories().get() -> QuadStateTextView.State.CHECKED.ordinal
 | 
			
		||||
                        in preferences.libraryUpdateCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal
 | 
			
		||||
                        in preferences.libraryUpdateCategories()
 | 
			
		||||
                            .get() -> QuadStateTextView.State.CHECKED.ordinal
 | 
			
		||||
                        in preferences.libraryUpdateCategoriesExclude()
 | 
			
		||||
                            .get() -> QuadStateTextView.State.INVERSED.ordinal
 | 
			
		||||
                        else -> QuadStateTextView.State.UNCHECKED.ordinal
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -121,7 +121,7 @@
 | 
			
		||||
    <string name="action_reset">Reset</string>
 | 
			
		||||
    <string name="action_undo">Undo</string>
 | 
			
		||||
    <string name="action_open_log">Open log</string>
 | 
			
		||||
    <string name="action_show_errors">Tap to see error details</string>
 | 
			
		||||
    <string name="action_show_errors">Tap to see details</string>
 | 
			
		||||
    <string name="action_create">Create</string>
 | 
			
		||||
    <string name="action_restore">Restore</string>
 | 
			
		||||
    <string name="action_webview_back">Back</string>
 | 
			
		||||
@@ -224,11 +224,11 @@
 | 
			
		||||
    <string name="charging">Charging</string>
 | 
			
		||||
    <string name="restrictions">Restrictions: %s</string>
 | 
			
		||||
 | 
			
		||||
    <string name="pref_library_update_manga_restriction">Library update restrictions</string>
 | 
			
		||||
    <string name="pref_library_update_manga_restriction">Skip updating</string>
 | 
			
		||||
    <string name="only_update_restrictions">Only update: %s</string>
 | 
			
		||||
    <string name="pref_update_only_completely_read">Completely read series</string>
 | 
			
		||||
    <string name="pref_update_only_non_completed">Ongoing series</string>
 | 
			
		||||
    <string name="pref_update_only_started">Started series</string>
 | 
			
		||||
    <string name="pref_update_only_completely_read">Has unread chapters</string>
 | 
			
		||||
    <string name="pref_update_only_non_completed">Is completed series</string>
 | 
			
		||||
    <string name="pref_update_only_started">No read chapters</string>
 | 
			
		||||
    <string name="pref_library_update_show_tab_badge">Show unread count on Updates icon</string>
 | 
			
		||||
    <string name="pref_library_update_refresh_metadata">Automatically refresh metadata</string>
 | 
			
		||||
    <string name="pref_library_update_refresh_metadata_summary">Check for new cover and details when updating library</string>
 | 
			
		||||
@@ -737,13 +737,13 @@
 | 
			
		||||
        <item quantity="one">Chapters %1$s and 1 more</item>
 | 
			
		||||
        <item quantity="other">Chapters %1$s and %2$d more</item>
 | 
			
		||||
    </plurals>
 | 
			
		||||
    <plurals name="notification_update_error">
 | 
			
		||||
        <item quantity="one">1 update failed</item>
 | 
			
		||||
        <item quantity="other">%1$d updates failed</item>
 | 
			
		||||
    </plurals>
 | 
			
		||||
    <string name="notification_update_skipped_error">%1$d update(s) skipped and %2$d update(s) failed</string>
 | 
			
		||||
    <string name="notification_cover_update_failed">Failed to update cover</string>
 | 
			
		||||
    <string name="notification_first_add_to_library">Please add the manga to your library before doing this</string>
 | 
			
		||||
    <string name="library_errors_help">For help on how to fix library update errors, see %1$s</string>
 | 
			
		||||
    <string name="skipped_reason_completed">Skipped because series is complete</string>
 | 
			
		||||
    <string name="skipped_reason_not_caught_up">Skipped because there are unread chapters</string>
 | 
			
		||||
    <string name="skipped_reason_not_started">Skipped because no chapters are read</string>
 | 
			
		||||
 | 
			
		||||
    <!-- File Picker Titles -->
 | 
			
		||||
    <string name="file_select_cover">Select cover image</string>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user