Migrate to multiplatform string resources (#10147)

* Migrate to multiplatform string resources

* Move plurals translations into separate files

* Fix lint check on generated files
This commit is contained in:
arkon
2023-11-18 13:54:56 -05:00
committed by GitHub
parent c39ae21f4a
commit 46e734fc8e
340 changed files with 5741 additions and 6292 deletions

View File

@@ -4,7 +4,6 @@ import android.Manifest
import android.content.Context
import android.net.Uri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_APP_PREFS
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_CATEGORY
import eu.kanade.tachiyomi.data.backup.BackupCreateFlags.BACKUP_CHAPTER
@@ -38,6 +37,7 @@ import logcat.LogPriority
import okio.buffer
import okio.gzip
import okio.sink
import tachiyomi.core.i18n.localize
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.system.logcat
@@ -49,6 +49,7 @@ import tachiyomi.domain.history.interactor.GetHistory
import tachiyomi.domain.manga.interactor.GetFavorites
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.FileOutputStream
@@ -75,7 +76,7 @@ class BackupCreator(
*/
suspend fun createBackup(uri: Uri, flags: Int, isAutoBackup: Boolean): String {
if (!context.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
throw IllegalStateException(context.getString(R.string.missing_storage_permission))
throw IllegalStateException(context.localize(MR.strings.missing_storage_permission))
}
val databaseManga = getFavorites.await()
@@ -110,7 +111,7 @@ class BackupCreator(
UniFile.fromUri(context, uri)
}
)
?: throw Exception(context.getString(R.string.create_backup_file_error))
?: throw Exception(context.localize(MR.strings.create_backup_file_error))
if (!file.isFile) {
throw IllegalStateException("Failed to get handle on a backup file")
@@ -118,7 +119,7 @@ class BackupCreator(
val byteArray = parser.encodeToByteArray(BackupSerializer, backup)
if (byteArray.isEmpty()) {
throw IllegalStateException(context.getString(R.string.empty_backup_error))
throw IllegalStateException(context.localize(MR.strings.empty_backup_error))
}
file.openOutputStream().also {

View File

@@ -2,10 +2,11 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.TrackerManager
import eu.kanade.tachiyomi.util.BackupUtil
import tachiyomi.core.i18n.localize
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -28,7 +29,7 @@ class BackupFileValidator(
}
if (backup.backupManga.isEmpty()) {
throw IllegalStateException(context.getString(R.string.invalid_backup_file_missing_manga))
throw IllegalStateException(context.localize(MR.strings.invalid_backup_file_missing_manga))
}
val sources = backup.backupSources.associate { it.sourceId to it.name }

View File

@@ -12,6 +12,9 @@ import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.i18n.localize
import tachiyomi.core.i18n.localizePlural
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.util.concurrent.TimeUnit
@@ -44,7 +47,7 @@ class BackupNotifier(private val context: Context) {
fun showBackupProgress(): NotificationCompat.Builder {
val builder = with(progressNotificationBuilder) {
setContentTitle(context.getString(R.string.creating_backup))
setContentTitle(context.localize(MR.strings.creating_backup))
setProgress(0, 0, true)
}
@@ -58,7 +61,7 @@ class BackupNotifier(private val context: Context) {
context.cancelNotification(Notifications.ID_BACKUP_PROGRESS)
with(completeNotificationBuilder) {
setContentTitle(context.getString(R.string.creating_backup_error))
setContentTitle(context.localize(MR.strings.creating_backup_error))
setContentText(error)
show(Notifications.ID_BACKUP_COMPLETE)
@@ -69,13 +72,13 @@ class BackupNotifier(private val context: Context) {
context.cancelNotification(Notifications.ID_BACKUP_PROGRESS)
with(completeNotificationBuilder) {
setContentTitle(context.getString(R.string.backup_created))
setContentTitle(context.localize(MR.strings.backup_created))
setContentText(unifile.filePath ?: unifile.name)
clearActions()
addAction(
R.drawable.ic_share_24dp,
context.getString(R.string.action_share),
context.localize(MR.strings.action_share),
NotificationReceiver.shareBackupPendingBroadcast(
context,
unifile.uri,
@@ -89,8 +92,8 @@ class BackupNotifier(private val context: Context) {
fun showRestoreProgress(
content: String = "",
contentTitle: String = context.getString(
R.string.restoring_backup,
contentTitle: String = context.localize(
MR.strings.restoring_backup,
),
progress: Int = 0,
maxAmount: Int = 100,
@@ -108,7 +111,7 @@ class BackupNotifier(private val context: Context) {
clearActions()
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
context.localize(MR.strings.action_cancel),
NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS),
)
}
@@ -122,7 +125,7 @@ class BackupNotifier(private val context: Context) {
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
with(completeNotificationBuilder) {
setContentTitle(context.getString(R.string.restoring_backup_error))
setContentTitle(context.localize(MR.strings.restoring_backup_error))
setContentText(error)
show(Notifications.ID_RESTORE_COMPLETE)
@@ -134,14 +137,14 @@ class BackupNotifier(private val context: Context) {
errorCount: Int,
path: String?,
file: String?,
contentTitle: String = context.getString(
R.string.restore_completed,
contentTitle: String = context.localize(
MR.strings.restore_completed,
),
) {
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)
val timeString = context.getString(
R.string.restore_duration,
val timeString = context.localize(
MR.strings.restore_duration,
TimeUnit.MILLISECONDS.toMinutes(time),
TimeUnit.MILLISECONDS.toSeconds(time) - TimeUnit.MINUTES.toSeconds(
TimeUnit.MILLISECONDS.toMinutes(time),
@@ -151,8 +154,8 @@ class BackupNotifier(private val context: Context) {
with(completeNotificationBuilder) {
setContentTitle(contentTitle)
setContentText(
context.resources.getQuantityString(
R.plurals.restore_completed_message,
context.localizePlural(
MR.plurals.restore_completed_message,
errorCount,
timeString,
errorCount,
@@ -168,7 +171,7 @@ class BackupNotifier(private val context: Context) {
setContentIntent(errorLogIntent)
addAction(
R.drawable.ic_folder_24dp,
context.getString(R.string.action_show_errors),
context.localize(MR.strings.action_show_errors),
errorLogIntent,
)
}

View File

@@ -9,14 +9,15 @@ import androidx.work.ForegroundInfo
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.isRunning
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.CancellationException
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.system.logcat
import tachiyomi.i18n.MR
class BackupRestoreJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
@@ -40,7 +41,7 @@ class BackupRestoreJob(private val context: Context, workerParams: WorkerParamet
Result.success()
} catch (e: Exception) {
if (e is CancellationException) {
notifier.showRestoreError(context.getString(R.string.restoring_backup_canceled))
notifier.showRestoreError(context.localize(MR.strings.restoring_backup_canceled))
Result.success()
} else {
logcat(LogPriority.ERROR, e)

View File

@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.backup
import android.content.Context
import android.net.Uri
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.backup.models.BackupHistory
import eu.kanade.tachiyomi.data.backup.models.BackupManga
@@ -23,6 +22,7 @@ import eu.kanade.tachiyomi.util.BackupUtil
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive
import tachiyomi.core.i18n.localize
import tachiyomi.core.preference.AndroidPreferenceStore
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.data.DatabaseHandler
@@ -37,6 +37,7 @@ import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@@ -93,7 +94,7 @@ class BackupRestorer(
errors.size,
logFile.parent,
logFile.name,
contentTitle = context.getString(R.string.library_sync_complete),
contentTitle = context.localize(MR.strings.library_sync_complete),
)
} else {
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
@@ -192,8 +193,8 @@ class BackupRestorer(
showRestoreProgress(
restoreProgress,
restoreAmount,
context.getString(R.string.categories),
context.getString(R.string.restoring_backup),
context.localize(MR.strings.categories),
context.localize(MR.strings.restoring_backup),
)
}
@@ -229,14 +230,14 @@ class BackupRestorer(
restoreProgress,
restoreAmount,
manga.title,
context.getString(R.string.syncing_library),
context.localize(MR.strings.syncing_library),
)
} else {
showRestoreProgress(
restoreProgress,
restoreAmount,
manga.title,
context.getString(R.string.restoring_backup),
context.localize(MR.strings.restoring_backup),
)
}
}
@@ -633,8 +634,8 @@ class BackupRestorer(
showRestoreProgress(
restoreProgress,
restoreAmount,
context.getString(R.string.app_settings),
context.getString(R.string.restoring_backup),
context.localize(MR.strings.app_settings),
context.localize(MR.strings.restoring_backup),
)
}
@@ -648,8 +649,8 @@ class BackupRestorer(
showRestoreProgress(
restoreProgress,
restoreAmount,
context.getString(R.string.source_settings),
context.getString(R.string.restoring_backup),
context.localize(MR.strings.source_settings),
context.localize(MR.strings.restoring_backup),
)
}

View File

@@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.data.download
import android.content.Context
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.Page
@@ -15,6 +14,7 @@ import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories
@@ -22,6 +22,7 @@ import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -158,7 +159,7 @@ class DownloadManager(
.filter { "image" in it.type.orEmpty() }
if (files.isEmpty()) {
throw Exception(context.getString(R.string.page_list_empty_error))
throw Exception(context.localize(MR.strings.page_list_empty_error))
}
return files.sortedBy { it.name }

View File

@@ -14,6 +14,8 @@ import eu.kanade.tachiyomi.util.lang.chop
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.util.regex.Pattern
@@ -78,13 +80,13 @@ internal class DownloadNotifier(private val context: Context) {
// Pause action
addAction(
R.drawable.ic_pause_24dp,
context.getString(R.string.action_pause),
context.localize(MR.strings.action_pause),
NotificationReceiver.pauseDownloadsPendingBroadcast(context),
)
}
val downloadingProgressText = context.getString(
R.string.chapter_downloading_progress,
val downloadingProgressText = context.localize(
MR.strings.chapter_downloading_progress,
download.downloadedImages,
download.pages!!.size,
)
@@ -115,8 +117,8 @@ internal class DownloadNotifier(private val context: Context) {
*/
fun onPaused() {
with(progressNotificationBuilder) {
setContentTitle(context.getString(R.string.chapter_paused))
setContentText(context.getString(R.string.download_notifier_download_paused))
setContentTitle(context.localize(MR.strings.chapter_paused))
setContentText(context.localize(MR.strings.download_notifier_download_paused))
setSmallIcon(R.drawable.ic_pause_24dp)
setProgress(0, 0, false)
setOngoing(false)
@@ -126,13 +128,13 @@ internal class DownloadNotifier(private val context: Context) {
// Resume action
addAction(
R.drawable.ic_play_arrow_24dp,
context.getString(R.string.action_resume),
context.localize(MR.strings.action_resume),
NotificationReceiver.resumeDownloadsPendingBroadcast(context),
)
// Clear action
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel_all),
context.localize(MR.strings.action_cancel_all),
NotificationReceiver.clearDownloadsPendingBroadcast(context),
)
@@ -162,7 +164,7 @@ internal class DownloadNotifier(private val context: Context) {
*/
fun onWarning(reason: String, timeout: Long? = null, contentIntent: PendingIntent? = null) {
with(errorNotificationBuilder) {
setContentTitle(context.getString(R.string.download_notifier_downloader_title))
setContentTitle(context.localize(MR.strings.download_notifier_downloader_title))
setStyle(NotificationCompat.BigTextStyle().bigText(reason))
setSmallIcon(R.drawable.ic_warning_white_24dp)
setAutoCancel(true)
@@ -190,9 +192,9 @@ internal class DownloadNotifier(private val context: Context) {
// Create notification
with(errorNotificationBuilder) {
setContentTitle(
mangaTitle?.plus(": $chapter") ?: context.getString(R.string.download_notifier_downloader_title),
mangaTitle?.plus(": $chapter") ?: context.localize(MR.strings.download_notifier_downloader_title),
)
setContentText(error ?: context.getString(R.string.download_notifier_unknown_error))
setContentText(error ?: context.localize(MR.strings.download_notifier_unknown_error))
setSmallIcon(R.drawable.ic_warning_white_24dp)
clearActions()
setContentIntent(NotificationHandler.openDownloadManagerPendingActivity(context))

View File

@@ -3,17 +3,18 @@ package eu.kanade.tachiyomi.data.download
import android.content.Context
import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.storage.DiskUtil
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -58,7 +59,7 @@ class DownloadProvider(
.createDirectory(getMangaDirName(mangaTitle))
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e) { "Invalid download directory" }
throw Exception(context.getString(R.string.invalid_location, downloadsDir))
throw Exception(context.localize(MR.strings.invalid_location, downloadsDir))
}
}

View File

@@ -6,9 +6,8 @@ import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.os.PowerManager
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import eu.kanade.tachiyomi.R
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
@@ -27,9 +26,11 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import logcat.LogPriority
import ru.beryukhov.reactivenetwork.ReactiveNetwork
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
/**
@@ -111,8 +112,8 @@ class DownloadService : Service() {
return null
}
private fun downloaderStop(@StringRes string: Int) {
downloadManager.downloaderStop(getString(string))
private fun downloaderStop(string: StringResource) {
downloadManager.downloaderStop(localize(string))
}
private fun listenNetworkChanges() {
@@ -122,20 +123,20 @@ class DownloadService : Service() {
withUIContext {
if (isOnline()) {
if (downloadPreferences.downloadOnlyOverWifi().get() && !isConnectedToWifi()) {
downloaderStop(R.string.download_notifier_text_only_wifi)
downloaderStop(MR.strings.download_notifier_text_only_wifi)
} else {
val started = downloadManager.downloaderStart()
if (!started) stopSelf()
}
} else {
downloaderStop(R.string.download_notifier_no_network)
downloaderStop(MR.strings.download_notifier_no_network)
}
}
}
.catch { error ->
withUIContext {
logcat(LogPriority.ERROR, error)
toast(R.string.download_queue_error)
toast(MR.strings.download_queue_error)
stopSelf()
}
}
@@ -144,7 +145,7 @@ class DownloadService : Service() {
private fun getPlaceholderNotification(): Notification {
return notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
setContentTitle(getString(R.string.download_notifier_downloader_title))
setContentTitle(localize(MR.strings.download_notifier_downloader_title))
}.build()
}
}

View File

@@ -4,7 +4,6 @@ import android.content.Context
import com.hippo.unifile.UniFile
import eu.kanade.domain.chapter.model.toSChapter
import eu.kanade.domain.manga.model.getComicInfo
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
@@ -41,6 +40,7 @@ import kotlinx.coroutines.supervisorScope
import logcat.LogPriority
import nl.adaptivity.xmlutil.serialization.XML
import okhttp3.Response
import tachiyomi.core.i18n.localize
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
import tachiyomi.core.metadata.comicinfo.ComicInfo
import tachiyomi.core.util.lang.launchIO
@@ -54,6 +54,7 @@ import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.BufferedOutputStream
@@ -302,7 +303,7 @@ class Downloader(
) {
withUIContext {
notifier.onWarning(
context.getString(R.string.download_queue_size_warning),
context.localize(MR.strings.download_queue_size_warning),
WARNING_NOTIF_TIMEOUT_MS,
NotificationHandler.openUrl(context, LibraryUpdateNotifier.HELP_WARNING_URL),
)
@@ -325,7 +326,7 @@ class Downloader(
if (availSpace != -1L && availSpace < MIN_DISK_SPACE) {
download.status = Download.State.ERROR
notifier.onError(
context.getString(R.string.download_insufficient_space),
context.localize(MR.strings.download_insufficient_space),
download.chapter.name,
download.manga.title,
)
@@ -342,7 +343,7 @@ class Downloader(
val pages = download.source.getPageList(download.chapter.toSChapter())
if (pages.isEmpty()) {
throw Exception(context.getString(R.string.page_list_empty_error))
throw Exception(context.localize(MR.strings.page_list_empty_error))
}
// Don't trust index from source
val reIndexedPages = pages.mapIndexed { index, page -> Page(index, page.url, page.imageUrl, page.uri) }
@@ -546,7 +547,7 @@ class Downloader(
try {
val filenamePrefix = String.format("%03d", page.number)
val imageFile = tmpDir.listFiles()?.firstOrNull { it.name.orEmpty().startsWith(filenamePrefix) }
?: error(context.getString(R.string.download_notifier_split_page_not_found, page.number))
?: error(context.localize(MR.strings.download_notifier_split_page_not_found, page.number))
// If the original page was previously split, then skip
if (imageFile.name.orEmpty().startsWith("${filenamePrefix}__")) return

View File

@@ -16,16 +16,13 @@ import androidx.work.WorkerParameters
import androidx.work.workDataOf
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.copyFrom
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
@@ -41,6 +38,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.preference.getAndSet
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
@@ -62,9 +60,9 @@ import tachiyomi.domain.manga.interactor.FetchInterval
import tachiyomi.domain.manga.interactor.GetLibraryManga
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.toMangaUpdate
import tachiyomi.domain.source.model.SourceNotInstalledException
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@@ -114,22 +112,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
logcat(LogPriority.ERROR, e) { "Not allowed to set foreground job" }
}
val target = inputData.getString(KEY_TARGET)?.let { Target.valueOf(it) } ?: Target.CHAPTERS
// If this is a chapter update, set the last update time to now
if (target == Target.CHAPTERS) {
libraryPreferences.lastUpdatedTimestamp().set(Date().time)
}
libraryPreferences.lastUpdatedTimestamp().set(Date().time)
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
addMangaToQueue(categoryId)
return withIOContext {
try {
when (target) {
Target.CHAPTERS -> updateChapterList()
Target.COVERS -> updateCovers()
}
updateChapterList()
Result.success()
} catch (e: Exception) {
if (e is CancellationException) {
@@ -191,29 +181,32 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.filter {
when {
it.manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_always_update))
skippedUpdates.add(it.manga to context.localize(MR.strings.skipped_reason_not_always_update))
false
}
MANGA_NON_COMPLETED in restrictions && it.manga.status.toInt() == SManga.COMPLETED -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_completed))
skippedUpdates.add(it.manga to context.localize(MR.strings.skipped_reason_completed))
false
}
MANGA_HAS_UNREAD in restrictions && it.unreadCount != 0L -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_caught_up))
skippedUpdates.add(it.manga to context.localize(MR.strings.skipped_reason_not_caught_up))
false
}
MANGA_NON_READ in restrictions && it.totalChapters > 0L && !it.hasStarted -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_started))
skippedUpdates.add(it.manga to context.localize(MR.strings.skipped_reason_not_started))
false
}
MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate > fetchWindow.second -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_in_release_period))
skippedUpdates.add(
it.manga to context.localize(MR.strings.skipped_reason_not_in_release_period),
)
false
}
else -> true
}
}
@@ -294,10 +287,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
} catch (e: Throwable) {
val errorMessage = when (e) {
is NoChaptersException -> context.getString(R.string.no_chapters_error)
is NoChaptersException -> context.localize(MR.strings.no_chapters_error)
// failedUpdates will already have the source, don't need to copy it into the message
is SourceNotInstalledException -> context.getString(
R.string.loader_not_implemented_error,
is SourceNotInstalledException -> context.localize(
MR.strings.loader_not_implemented_error,
)
else -> e.message
}
@@ -359,51 +352,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
return syncChaptersWithSource.await(chapters, dbManga, source, false, fetchWindow)
}
private suspend fun updateCovers() {
val semaphore = Semaphore(5)
val progressCount = AtomicInteger(0)
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
coroutineScope {
mangaToUpdate.groupBy { it.manga.source }
.values
.map { mangaInSource ->
async {
semaphore.withPermit {
mangaInSource.forEach { libraryManga ->
val manga = libraryManga.manga
ensureActive()
withUpdateNotification(
currentlyUpdatingManga,
progressCount,
manga,
) {
val source = sourceManager.get(manga.source) ?: return@withUpdateNotification
try {
val networkManga = source.getMangaDetails(manga.toSManga())
val updatedManga = manga.prepUpdateCover(coverCache, networkManga, true)
.copyFrom(networkManga)
try {
updateManga.await(updatedManga.toMangaUpdate())
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Manga doesn't exist anymore" }
}
} catch (e: Throwable) {
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
}
}
}
}
}
.awaitAll()
}
notifier.cancelProgressNotification()
}
private suspend fun withUpdateNotification(
updatingManga: CopyOnWriteArrayList<Manga>,
completed: AtomicInteger,
@@ -440,7 +388,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
if (errors.isNotEmpty()) {
val file = context.createFileInCacheDir("tachiyomi_update_errors.txt")
file.bufferedWriter().use { out ->
out.write(context.getString(R.string.library_errors_help, ERROR_LOG_HELP_URL) + "\n\n")
out.write(context.localize(MR.strings.library_errors_help, ERROR_LOG_HELP_URL) + "\n\n")
// Error file format:
// ! Error
// # Source
@@ -462,14 +410,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
return File("")
}
/**
* Defines what should be updated within a service execution.
*/
enum class Target {
CHAPTERS, // Manga chapters
COVERS, // Manga covers
}
companion object {
private const val TAG = "LibraryUpdate"
private const val WORK_NAME_AUTO = "LibraryUpdate-auto"
@@ -484,11 +424,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
*/
private const val KEY_CATEGORY = "category"
/**
* Key that defines what should be updated.
*/
private const val KEY_TARGET = "target"
fun cancelAllWorks(context: Context) {
context.workManager.cancelAllWorkByTag(TAG)
}
@@ -534,7 +469,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
fun startNow(
context: Context,
category: Category? = null,
target: Target = Target.CHAPTERS,
): Boolean {
val wm = context.workManager
if (wm.isRunning(TAG)) {
@@ -544,7 +478,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val inputData = workDataOf(
KEY_CATEGORY to category?.id,
KEY_TARGET to target.name,
)
val request = OneTimeWorkRequestBuilder<LibraryUpdateJob>()
.addTag(TAG)

View File

@@ -26,9 +26,12 @@ import eu.kanade.tachiyomi.util.system.getBitmapOrNull
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.Constants
import tachiyomi.core.i18n.localize
import tachiyomi.core.i18n.localizePlural
import tachiyomi.core.util.lang.launchUI
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.text.NumberFormat
@@ -58,12 +61,12 @@ class LibraryUpdateNotifier(private val context: Context) {
*/
val progressNotificationBuilder by lazy {
context.notificationBuilder(Notifications.CHANNEL_LIBRARY_PROGRESS) {
setContentTitle(context.getString(R.string.app_name))
setContentTitle(context.localize(MR.strings.app_name))
setSmallIcon(R.drawable.ic_refresh_24dp)
setLargeIcon(notificationBitmap)
setOngoing(true)
setOnlyAlertOnce(true)
addAction(R.drawable.ic_close_24dp, context.getString(R.string.action_cancel), cancelIntent)
addAction(R.drawable.ic_close_24dp, context.localize(MR.strings.action_cancel), cancelIntent)
}
}
@@ -77,14 +80,14 @@ class LibraryUpdateNotifier(private val context: Context) {
fun showProgressNotification(manga: List<Manga>, current: Int, total: Int) {
if (preferences.hideNotificationContent().get()) {
progressNotificationBuilder
.setContentTitle(context.getString(R.string.notification_check_updates))
.setContentTitle(context.localize(MR.strings.notification_check_updates))
.setContentText("($current/$total)")
} else {
val updatingText = manga.joinToString("\n") { it.title.chop(40) }
progressNotificationBuilder
.setContentTitle(
context.getString(
R.string.notification_updating_progress,
context.localize(
MR.strings.notification_updating_progress,
percentFormatter.format(current.toFloat() / total),
),
)
@@ -104,8 +107,8 @@ class LibraryUpdateNotifier(private val context: Context) {
Notifications.ID_LIBRARY_SIZE_WARNING,
Notifications.CHANNEL_LIBRARY_PROGRESS,
) {
setContentTitle(context.getString(R.string.label_warning))
setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(R.string.notification_size_warning)))
setContentTitle(context.localize(MR.strings.label_warning))
setStyle(NotificationCompat.BigTextStyle().bigText(context.localize(MR.strings.notification_size_warning)))
setSmallIcon(R.drawable.ic_warning_white_24dp)
setTimeoutAfter(Downloader.WARNING_NOTIF_TIMEOUT_MS)
setContentIntent(NotificationHandler.openUrl(context, HELP_WARNING_URL))
@@ -127,8 +130,8 @@ class LibraryUpdateNotifier(private val context: Context) {
Notifications.ID_LIBRARY_ERROR,
Notifications.CHANNEL_LIBRARY_ERROR,
) {
setContentTitle(context.resources.getString(R.string.notification_update_error, failed))
setContentText(context.getString(R.string.action_show_errors))
setContentTitle(context.localize(MR.strings.notification_update_error, failed))
setContentText(context.localize(MR.strings.action_show_errors))
setSmallIcon(R.drawable.ic_tachi)
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
@@ -149,8 +152,8 @@ class LibraryUpdateNotifier(private val context: Context) {
Notifications.ID_LIBRARY_SKIPPED,
Notifications.CHANNEL_LIBRARY_SKIPPED,
) {
setContentTitle(context.resources.getString(R.string.notification_update_skipped, skipped))
setContentText(context.getString(R.string.learn_more))
setContentTitle(context.localize(MR.strings.notification_update_skipped, skipped))
setContentText(context.localize(MR.strings.learn_more))
setSmallIcon(R.drawable.ic_tachi)
setContentIntent(NotificationHandler.openUrl(context, HELP_SKIPPED_URL))
}
@@ -167,13 +170,13 @@ class LibraryUpdateNotifier(private val context: Context) {
Notifications.ID_NEW_CHAPTERS,
Notifications.CHANNEL_NEW_CHAPTERS,
) {
setContentTitle(context.getString(R.string.notification_new_chapters))
setContentTitle(context.localize(MR.strings.notification_new_chapters))
if (updates.size == 1 && !preferences.hideNotificationContent().get()) {
setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
} else {
setContentText(
context.resources.getQuantityString(
R.plurals.notification_new_chapters_summary,
context.localizePlural(
MR.plurals.notification_new_chapters_summary,
updates.size,
updates.size,
),
@@ -243,7 +246,7 @@ class LibraryUpdateNotifier(private val context: Context) {
// Mark chapters as read action
addAction(
R.drawable.ic_glasses_24dp,
context.getString(R.string.action_mark_as_read),
context.localize(MR.strings.action_mark_as_read),
NotificationReceiver.markAsReadPendingBroadcast(
context,
manga,
@@ -254,7 +257,7 @@ class LibraryUpdateNotifier(private val context: Context) {
// View chapters action
addAction(
R.drawable.ic_book_24dp,
context.getString(R.string.action_view_chapters),
context.localize(MR.strings.action_view_chapters),
NotificationReceiver.openChapterPendingActivity(
context,
manga,
@@ -266,7 +269,7 @@ class LibraryUpdateNotifier(private val context: Context) {
if (chapters.size <= Downloader.CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download),
context.localize(MR.strings.action_download),
NotificationReceiver.downloadChaptersPendingBroadcast(
context,
manga,
@@ -306,8 +309,8 @@ class LibraryUpdateNotifier(private val context: Context) {
// No sensible chapter numbers to show (i.e. no chapters have parsed chapter number)
0 -> {
// "1 new chapter" or "5 new chapters"
context.resources.getQuantityString(
R.plurals.notification_chapters_generic,
context.localizePlural(
MR.plurals.notification_chapters_generic,
chapters.size,
chapters.size,
)
@@ -317,14 +320,14 @@ class LibraryUpdateNotifier(private val context: Context) {
val remaining = chapters.size - displayableChapterNumbers.size
if (remaining == 0) {
// "Chapter 2.5"
context.resources.getString(
R.string.notification_chapters_single,
context.localize(
MR.strings.notification_chapters_single,
displayableChapterNumbers.first(),
)
} else {
// "Chapter 2.5 and 10 more"
context.resources.getString(
R.string.notification_chapters_single_and_more,
context.localize(
MR.strings.notification_chapters_single_and_more,
displayableChapterNumbers.first(),
remaining,
)
@@ -339,16 +342,16 @@ class LibraryUpdateNotifier(private val context: Context) {
val joinedChapterNumbers = displayableChapterNumbers
.take(NOTIF_MAX_CHAPTERS)
.joinToString(", ")
context.resources.getQuantityString(
R.plurals.notification_chapters_multiple_and_more,
context.localizePlural(
MR.plurals.notification_chapters_multiple_and_more,
remaining,
joinedChapterNumbers,
remaining,
)
} else {
// "Chapters 1, 2.5, 3"
context.resources.getString(
R.string.notification_chapters_multiple,
context.localize(
MR.strings.notification_chapters_multiple,
displayableChapterNumbers.joinToString(", "),
)
}

View File

@@ -0,0 +1,210 @@
package eu.kanade.tachiyomi.data.library
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.WorkQuery
import androidx.work.WorkerParameters
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.copyFrom
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.system.isRunning
import eu.kanade.tachiyomi.util.system.workManager
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.interactor.GetLibraryManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.toMangaUpdate
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicInteger
class MetadataUpdateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val sourceManager: SourceManager = Injekt.get()
private val coverCache: CoverCache = Injekt.get()
private val getLibraryManga: GetLibraryManga = Injekt.get()
private val updateManga: UpdateManga = Injekt.get()
private val notifier = LibraryUpdateNotifier(context)
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
override suspend fun doWork(): Result {
try {
setForeground(getForegroundInfo())
} catch (e: IllegalStateException) {
logcat(LogPriority.ERROR, e) { "Not allowed to set foreground job" }
}
addMangaToQueue()
return withIOContext {
try {
updateMetadata()
Result.success()
} catch (e: Exception) {
if (e is CancellationException) {
// Assume success although cancelled
Result.success()
} else {
logcat(LogPriority.ERROR, e)
Result.failure()
}
} finally {
notifier.cancelProgressNotification()
}
}
}
override suspend fun getForegroundInfo(): ForegroundInfo {
val notifier = LibraryUpdateNotifier(context)
return ForegroundInfo(
Notifications.ID_LIBRARY_PROGRESS,
notifier.progressNotificationBuilder.build(),
)
}
/**
* Adds list of manga to be updated.
*/
private fun addMangaToQueue() {
mangaToUpdate = runBlocking { getLibraryManga.await() }
// Warn when excessively checking a single source
val maxUpdatesFromSource = mangaToUpdate
.groupBy { it.manga.source }
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
.maxOfOrNull { it.value.size } ?: 0
if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
notifier.showQueueSizeWarningNotification()
}
}
private suspend fun updateMetadata() {
val semaphore = Semaphore(5)
val progressCount = AtomicInteger(0)
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
coroutineScope {
mangaToUpdate.groupBy { it.manga.source }
.values
.map { mangaInSource ->
async {
semaphore.withPermit {
mangaInSource.forEach { libraryManga ->
val manga = libraryManga.manga
ensureActive()
withUpdateNotification(
currentlyUpdatingManga,
progressCount,
manga,
) {
val source = sourceManager.get(manga.source) ?: return@withUpdateNotification
try {
val networkManga = source.getMangaDetails(manga.toSManga())
val updatedManga = manga.prepUpdateCover(coverCache, networkManga, true)
.copyFrom(networkManga)
try {
updateManga.await(updatedManga.toMangaUpdate())
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Manga doesn't exist anymore" }
}
} catch (e: Throwable) {
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
}
}
}
}
}
.awaitAll()
}
notifier.cancelProgressNotification()
}
private suspend fun withUpdateNotification(
updatingManga: CopyOnWriteArrayList<Manga>,
completed: AtomicInteger,
manga: Manga,
block: suspend () -> Unit,
) = coroutineScope {
ensureActive()
updatingManga.add(manga)
notifier.showProgressNotification(
updatingManga,
completed.get(),
mangaToUpdate.size,
)
block()
ensureActive()
updatingManga.remove(manga)
completed.getAndIncrement()
notifier.showProgressNotification(
updatingManga,
completed.get(),
mangaToUpdate.size,
)
}
companion object {
private const val TAG = "MetadataUpdate"
private const val WORK_NAME_MANUAL = "MetadataUpdate"
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
fun startNow(context: Context): Boolean {
val wm = context.workManager
if (wm.isRunning(TAG)) {
// Already running either as a scheduled or manual job
return false
}
val request = OneTimeWorkRequestBuilder<MetadataUpdateJob>()
.addTag(TAG)
.addTag(WORK_NAME_MANUAL)
.build()
wm.enqueueUniqueWork(WORK_NAME_MANUAL, ExistingWorkPolicy.KEEP, request)
return true
}
fun stop(context: Context) {
val wm = context.workManager
val workQuery = WorkQuery.Builder.fromTags(listOf(TAG))
.addStates(listOf(WorkInfo.State.RUNNING))
.build()
wm.getWorkInfos(workQuery).get()
// Should only return one work but just in case
.forEach {
wm.cancelWorkById(it.id)
}
}
}
}

View File

@@ -7,7 +7,6 @@ import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
@@ -32,6 +31,7 @@ import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@@ -173,7 +173,7 @@ class NotificationReceiver : BroadcastReceiver() {
}
context.startActivity(intent)
} else {
context.toast(R.string.chapter_error)
context.toast(MR.strings.chapter_error)
}
}

View File

@@ -5,9 +5,10 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_DEFAULT
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_HIGH
import androidx.core.app.NotificationManagerCompat.IMPORTANCE_LOW
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.buildNotificationChannel
import eu.kanade.tachiyomi.util.system.buildNotificationChannelGroup
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
/**
* Class to manage the basic information of all the notifications used in the app.
@@ -102,16 +103,16 @@ object Notifications {
notificationManager.createNotificationChannelGroupsCompat(
listOf(
buildNotificationChannelGroup(GROUP_BACKUP_RESTORE) {
setName(context.getString(R.string.label_backup))
setName(context.localize(MR.strings.label_backup))
},
buildNotificationChannelGroup(GROUP_DOWNLOADER) {
setName(context.getString(R.string.download_notifier_downloader_title))
setName(context.localize(MR.strings.download_notifier_downloader_title))
},
buildNotificationChannelGroup(GROUP_LIBRARY) {
setName(context.getString(R.string.label_library))
setName(context.localize(MR.strings.label_library))
},
buildNotificationChannelGroup(GROUP_APK_UPDATES) {
setName(context.getString(R.string.label_recent_updates))
setName(context.localize(MR.strings.label_recent_updates))
},
),
)
@@ -119,57 +120,57 @@ object Notifications {
notificationManager.createNotificationChannelsCompat(
listOf(
buildNotificationChannel(CHANNEL_COMMON, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_common))
setName(context.localize(MR.strings.channel_common))
},
buildNotificationChannel(CHANNEL_LIBRARY_PROGRESS, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_progress))
setName(context.localize(MR.strings.channel_progress))
setGroup(GROUP_LIBRARY)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_LIBRARY_ERROR, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_errors))
setName(context.localize(MR.strings.channel_errors))
setGroup(GROUP_LIBRARY)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_LIBRARY_SKIPPED, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_skipped))
setName(context.localize(MR.strings.channel_skipped))
setGroup(GROUP_LIBRARY)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_NEW_CHAPTERS, IMPORTANCE_DEFAULT) {
setName(context.getString(R.string.channel_new_chapters))
setName(context.localize(MR.strings.channel_new_chapters))
},
buildNotificationChannel(CHANNEL_DOWNLOADER_PROGRESS, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_progress))
setName(context.localize(MR.strings.channel_progress))
setGroup(GROUP_DOWNLOADER)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_DOWNLOADER_ERROR, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_errors))
setName(context.localize(MR.strings.channel_errors))
setGroup(GROUP_DOWNLOADER)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_BACKUP_RESTORE_PROGRESS, IMPORTANCE_LOW) {
setName(context.getString(R.string.channel_progress))
setName(context.localize(MR.strings.channel_progress))
setGroup(GROUP_BACKUP_RESTORE)
setShowBadge(false)
},
buildNotificationChannel(CHANNEL_BACKUP_RESTORE_COMPLETE, IMPORTANCE_HIGH) {
setName(context.getString(R.string.channel_complete))
setName(context.localize(MR.strings.channel_complete))
setGroup(GROUP_BACKUP_RESTORE)
setShowBadge(false)
setSound(null, null)
},
buildNotificationChannel(CHANNEL_INCOGNITO_MODE, IMPORTANCE_LOW) {
setName(context.getString(R.string.pref_incognito_mode))
setName(context.localize(MR.strings.pref_incognito_mode))
},
buildNotificationChannel(CHANNEL_APP_UPDATE, IMPORTANCE_DEFAULT) {
setGroup(GROUP_APK_UPDATES)
setName(context.getString(R.string.channel_app_updates))
setName(context.localize(MR.strings.channel_app_updates))
},
buildNotificationChannel(CHANNEL_EXTENSIONS_UPDATE, IMPORTANCE_DEFAULT) {
setGroup(GROUP_APK_UPDATES)
setName(context.getString(R.string.channel_ext_updates))
setName(context.localize(MR.strings.channel_ext_updates))
},
),
)

View File

@@ -10,14 +10,15 @@ import android.provider.MediaStore
import androidx.annotation.RequiresApi
import androidx.core.content.contentValuesOf
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir
import eu.kanade.tachiyomi.util.storage.getUriCompat
import logcat.LogPriority
import okio.IOException
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.system.ImageUtil
import tachiyomi.core.util.system.logcat
import tachiyomi.i18n.MR
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
@@ -70,7 +71,7 @@ class ImageSaver(
val imageLocation = (image.location as Location.Pictures).relativePath
val relativePath = listOf(
Environment.DIRECTORY_PICTURES,
context.getString(R.string.app_name),
context.localize(MR.strings.app_name),
imageLocation,
).joinToString(File.separator)
@@ -85,7 +86,7 @@ class ImageSaver(
context.contentResolver.insert(
pictureDir,
contentValues,
) ?: throw IOException(context.getString(R.string.error_saving_picture))
) ?: throw IOException(context.localize(MR.strings.error_saving_picture))
}
try {
@@ -96,7 +97,7 @@ class ImageSaver(
}
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
throw IOException(context.getString(R.string.error_saving_picture))
throw IOException(context.localize(MR.strings.error_saving_picture))
}
DiskUtil.scanMedia(context, picture)
@@ -184,7 +185,7 @@ sealed interface Location {
is Pictures -> {
val file = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
context.getString(R.string.app_name),
context.localize(MR.strings.app_name),
)
if (relativePath.isNotEmpty()) {
return File(

View File

@@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.track
import androidx.annotation.CallSuper
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
@@ -28,8 +28,7 @@ interface Tracker {
fun getStatusList(): List<Int>
@StringRes
fun getStatus(status: Int): Int?
fun getStatus(status: Int): StringResource?
fun getReadingStatus(): Int

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.anilist
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -12,6 +12,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import tachiyomi.domain.track.model.Track as DomainTrack
@@ -60,14 +61,13 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
}
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
READING -> R.string.reading
PLAN_TO_READ -> R.string.plan_to_read
COMPLETED -> R.string.completed
ON_HOLD -> R.string.on_hold
DROPPED -> R.string.dropped
REREADING -> R.string.repeating
override fun getStatus(status: Int): StringResource? = when (status) {
READING -> MR.strings.reading
PLAN_TO_READ -> MR.strings.plan_to_read
COMPLETED -> MR.strings.completed
ON_HOLD -> MR.strings.on_hold
DROPPED -> MR.strings.dropped
REREADING -> MR.strings.repeating
else -> null
}

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.bangumi
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -10,6 +10,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
@@ -89,13 +90,12 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
}
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
READING -> R.string.reading
PLAN_TO_READ -> R.string.plan_to_read
COMPLETED -> R.string.completed
ON_HOLD -> R.string.on_hold
DROPPED -> R.string.dropped
override fun getStatus(status: Int): StringResource? = when (status) {
READING -> MR.strings.reading
PLAN_TO_READ -> MR.strings.plan_to_read
COMPLETED -> MR.strings.completed
ON_HOLD -> MR.strings.on_hold
DROPPED -> MR.strings.dropped
else -> null
}

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.kavita
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -14,6 +14,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.security.MessageDigest
import tachiyomi.domain.track.model.Track as DomainTrack
@@ -39,11 +40,10 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
UNREAD -> R.string.unread
READING -> R.string.reading
COMPLETED -> R.string.completed
override fun getStatus(status: Int): StringResource? = when (status) {
UNREAD -> MR.strings.unread
READING -> MR.strings.reading
COMPLETED -> MR.strings.completed
else -> null
}

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.kitsu
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -11,6 +11,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.text.DecimalFormat
@@ -40,13 +41,12 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
}
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
READING -> R.string.reading
PLAN_TO_READ -> R.string.plan_to_read
COMPLETED -> R.string.completed
ON_HOLD -> R.string.on_hold
DROPPED -> R.string.dropped
override fun getStatus(status: Int): StringResource? = when (status) {
READING -> MR.strings.reading
PLAN_TO_READ -> MR.strings.plan_to_read
COMPLETED -> MR.strings.completed
ON_HOLD -> MR.strings.on_hold
DROPPED -> MR.strings.dropped
else -> null
}

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.komga
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -13,6 +13,7 @@ import kotlinx.collections.immutable.persistentListOf
import okhttp3.Dns
import okhttp3.OkHttpClient
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.domain.track.model.Track as DomainTrack
class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
@@ -36,11 +37,10 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
UNREAD -> R.string.unread
READING -> R.string.reading
COMPLETED -> R.string.completed
override fun getStatus(status: Int): StringResource? = when (status) {
UNREAD -> MR.strings.unread
READING -> MR.strings.reading
COMPLETED -> MR.strings.completed
else -> null
}

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.mangaupdates
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import tachiyomi.i18n.MR
class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker {
@@ -40,13 +41,12 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST)
}
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
READING_LIST -> R.string.reading_list
WISH_LIST -> R.string.wish_list
COMPLETE_LIST -> R.string.complete_list
ON_HOLD_LIST -> R.string.on_hold_list
UNFINISHED_LIST -> R.string.unfinished_list
override fun getStatus(status: Int): StringResource? = when (status) {
READING_LIST -> MR.strings.reading_list
WISH_LIST -> MR.strings.wish_list
COMPLETE_LIST -> MR.strings.complete_list
ON_HOLD_LIST -> MR.strings.on_hold_list
UNFINISHED_LIST -> MR.strings.unfinished_list
else -> null
}

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.myanimelist
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -11,6 +11,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
@@ -46,14 +47,13 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
}
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
READING -> R.string.reading
PLAN_TO_READ -> R.string.plan_to_read
COMPLETED -> R.string.completed
ON_HOLD -> R.string.on_hold
DROPPED -> R.string.dropped
REREADING -> R.string.repeating
override fun getStatus(status: Int): StringResource? = when (status) {
READING -> MR.strings.reading
PLAN_TO_READ -> MR.strings.plan_to_read
COMPLETED -> MR.strings.completed
ON_HOLD -> MR.strings.on_hold
DROPPED -> MR.strings.dropped
REREADING -> MR.strings.repeating
else -> null
}

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.shikimori
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -11,6 +11,7 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
@@ -103,14 +104,13 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
}
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
READING -> R.string.reading
PLAN_TO_READ -> R.string.plan_to_read
COMPLETED -> R.string.completed
ON_HOLD -> R.string.on_hold
DROPPED -> R.string.dropped
REREADING -> R.string.repeating
override fun getStatus(status: Int): StringResource? = when (status) {
READING -> MR.strings.reading
PLAN_TO_READ -> MR.strings.plan_to_read
COMPLETED -> MR.strings.completed
ON_HOLD -> MR.strings.on_hold
DROPPED -> MR.strings.dropped
REREADING -> MR.strings.repeating
else -> null
}

View File

@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.track.suwayomi
import android.graphics.Color
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.BaseTracker
@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.source.Source
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.domain.manga.model.Manga as DomainManga
import tachiyomi.domain.track.model.Track as DomainTrack
@@ -29,11 +30,10 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
@StringRes
override fun getStatus(status: Int): Int? = when (status) {
UNREAD -> R.string.unread
READING -> R.string.reading
COMPLETED -> R.string.completed
override fun getStatus(status: Int): StringResource? = when (status) {
UNREAD -> MR.strings.unread
READING -> MR.strings.reading
COMPLETED -> MR.strings.completed
else -> null
}

View File

@@ -9,7 +9,6 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
@@ -22,8 +21,10 @@ import eu.kanade.tachiyomi.util.system.workManager
import logcat.LogPriority
import okhttp3.internal.http2.ErrorCode
import okhttp3.internal.http2.StreamResetException
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.io.File
import kotlin.coroutines.cancellation.CancellationException
@@ -36,7 +37,7 @@ class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerPar
override suspend fun doWork(): Result {
val url = inputData.getString(EXTRA_DOWNLOAD_URL)
val title = inputData.getString(EXTRA_DOWNLOAD_TITLE) ?: context.getString(R.string.app_name)
val title = inputData.getString(EXTRA_DOWNLOAD_TITLE) ?: context.localize(MR.strings.app_name)
if (url.isNullOrEmpty()) {
return Result.failure()

View File

@@ -13,7 +13,9 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.i18n.localize
import tachiyomi.domain.release.model.Release
import tachiyomi.i18n.MR
internal class AppUpdateNotifier(private val context: Context) {
@@ -51,7 +53,7 @@ internal class AppUpdateNotifier(private val context: Context) {
}
with(notificationBuilder) {
setContentTitle(context.getString(R.string.update_check_notification_update_available))
setContentTitle(context.localize(MR.strings.update_check_notification_update_available))
setContentText(release.version)
setSmallIcon(android.R.drawable.stat_sys_download_done)
setContentIntent(updateIntent)
@@ -59,12 +61,12 @@ internal class AppUpdateNotifier(private val context: Context) {
clearActions()
addAction(
android.R.drawable.stat_sys_download_done,
context.getString(R.string.action_download),
context.localize(MR.strings.action_download),
updateIntent,
)
addAction(
R.drawable.ic_info_24dp,
context.getString(R.string.whats_new),
context.localize(MR.strings.whats_new),
releaseIntent,
)
}
@@ -79,14 +81,14 @@ internal class AppUpdateNotifier(private val context: Context) {
fun onDownloadStarted(title: String? = null): NotificationCompat.Builder {
with(notificationBuilder) {
title?.let { setContentTitle(title) }
setContentText(context.getString(R.string.update_check_notification_download_in_progress))
setContentText(context.localize(MR.strings.update_check_notification_download_in_progress))
setSmallIcon(android.R.drawable.stat_sys_download)
setOngoing(true)
clearActions()
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
context.localize(MR.strings.action_cancel),
NotificationReceiver.cancelDownloadAppUpdatePendingBroadcast(context),
)
}
@@ -115,7 +117,7 @@ internal class AppUpdateNotifier(private val context: Context) {
fun promptInstall(uri: Uri) {
val installIntent = NotificationHandler.installApkPendingActivity(context, uri)
with(notificationBuilder) {
setContentText(context.getString(R.string.update_check_notification_download_complete))
setContentText(context.localize(MR.strings.update_check_notification_download_complete))
setSmallIcon(android.R.drawable.stat_sys_download_done)
setOnlyAlertOnce(false)
setProgress(0, 0, false)
@@ -125,12 +127,12 @@ internal class AppUpdateNotifier(private val context: Context) {
clearActions()
addAction(
R.drawable.ic_system_update_alt_white_24dp,
context.getString(R.string.action_install),
context.localize(MR.strings.action_install),
installIntent,
)
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
context.localize(MR.strings.action_cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATE_PROMPT),
)
}
@@ -145,8 +147,8 @@ internal class AppUpdateNotifier(private val context: Context) {
*/
fun promptFdroidUpdate() {
with(notificationBuilder) {
setContentTitle(context.getString(R.string.update_check_notification_update_available))
setContentText(context.getString(R.string.update_check_fdroid_migration_info))
setContentTitle(context.localize(MR.strings.update_check_notification_update_available))
setContentText(context.localize(MR.strings.update_check_fdroid_migration_info))
setSmallIcon(R.drawable.ic_tachi)
setContentIntent(
NotificationHandler.openUrl(
@@ -165,7 +167,7 @@ internal class AppUpdateNotifier(private val context: Context) {
*/
fun onDownloadError(url: String) {
with(notificationBuilder) {
setContentText(context.getString(R.string.update_check_notification_download_error))
setContentText(context.localize(MR.strings.update_check_notification_download_error))
setSmallIcon(R.drawable.ic_warning_white_24dp)
setOnlyAlertOnce(false)
setProgress(0, 0, false)
@@ -173,12 +175,12 @@ internal class AppUpdateNotifier(private val context: Context) {
clearActions()
addAction(
R.drawable.ic_refresh_24dp,
context.getString(R.string.action_retry),
context.localize(MR.strings.action_retry),
NotificationReceiver.downloadAppUpdatePendingBroadcast(context, url),
)
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
context.localize(MR.strings.action_cancel),
NotificationReceiver.dismissNotificationPendingBroadcast(context, Notifications.ID_APP_UPDATER),
)
}