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
+4 -2
View File
@@ -55,7 +55,9 @@ import org.acra.config.httpSender
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.conscrypt.Conscrypt
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.system.logcat
import tachiyomi.i18n.MR
import tachiyomi.presentation.widget.WidgetManager
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -104,8 +106,8 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
Notifications.ID_INCOGNITO_MODE,
Notifications.CHANNEL_INCOGNITO_MODE,
) {
setContentTitle(getString(R.string.pref_incognito_mode))
setContentText(getString(R.string.notification_incognito_text))
setContentTitle(localize(MR.strings.pref_incognito_mode))
setContentText(localize(MR.strings.notification_incognito_text))
setSmallIcon(R.drawable.ic_glasses_24dp)
setOngoing(true)
@@ -27,6 +27,7 @@ import tachiyomi.core.preference.plusAssign
import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.MANGA_NON_COMPLETED
import tachiyomi.i18n.MR
import java.io.File
object Migrations {
@@ -146,7 +147,7 @@ object Migrations {
// v53: switched from WebView to OAuth
if (trackerManager.myAnimeList.isLoggedIn) {
trackerManager.myAnimeList.logout()
context.toast(R.string.myanimelist_relogin)
context.toast(MR.strings.myanimelist_relogin)
}
}
if (oldVersion < 57) {
@@ -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 {
@@ -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 }
@@ -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,
)
}
@@ -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)
@@ -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),
)
}
@@ -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 }
@@ -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))
@@ -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))
}
}
@@ -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()
}
}
@@ -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
@@ -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)
@@ -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(", "),
)
}
@@ -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)
}
}
}
}
@@ -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)
}
}
@@ -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))
},
),
)
@@ -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(
@@ -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
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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()
@@ -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),
)
}
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension
import android.content.Context
import android.graphics.drawable.Drawable
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.extension.api.ExtensionUpdateNotifier
import eu.kanade.tachiyomi.extension.model.Extension
@@ -24,6 +23,7 @@ import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.source.model.StubSource
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Locale
@@ -121,7 +121,7 @@ class ExtensionManager(
api.findExtensions()
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
withUIContext { context.toast(R.string.extension_api_error) }
withUIContext { context.toast(MR.strings.extension_api_error) }
emptyList()
}
@@ -7,6 +7,8 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.i18n.localizePlural
import tachiyomi.i18n.MR
class ExtensionUpdateNotifier(private val context: Context) {
@@ -16,8 +18,8 @@ class ExtensionUpdateNotifier(private val context: Context) {
Notifications.CHANNEL_EXTENSIONS_UPDATE,
) {
setContentTitle(
context.resources.getQuantityString(
R.plurals.update_check_notification_ext_updates,
context.localizePlural(
MR.plurals.update_check_notification_ext_updates,
names.size,
names.size,
),
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.installer
import android.app.Service
import android.content.pm.PackageManager
import android.os.Build
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.util.system.getUriSize
import eu.kanade.tachiyomi.util.system.toast
@@ -15,6 +14,7 @@ import kotlinx.coroutines.launch
import logcat.LogPriority
import rikka.shizuku.Shizuku
import tachiyomi.core.util.system.logcat
import tachiyomi.i18n.MR
import java.io.BufferedReader
import java.io.InputStream
@@ -116,7 +116,7 @@ class ShizukuInstaller(private val service: Service) : Installer(service) {
}
} else {
logcat(LogPriority.ERROR) { "Shizuku is not ready to use" }
service.toast(R.string.ext_installer_shizuku_stopped)
service.toast(MR.strings.ext_installer_shizuku_stopped)
service.stopSelf()
false
}
@@ -15,7 +15,9 @@ import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOW
import eu.kanade.tachiyomi.util.system.getSerializableExtraCompat
import eu.kanade.tachiyomi.util.system.notificationBuilder
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.system.logcat
import tachiyomi.i18n.MR
class ExtensionInstallService : Service() {
@@ -27,7 +29,7 @@ class ExtensionInstallService : Service() {
setAutoCancel(false)
setOngoing(true)
setShowWhen(false)
setContentTitle(getString(R.string.ext_install_service_notif))
setContentTitle(localize(MR.strings.ext_install_service_notif))
setProgress(100, 100, true)
}.build()
startForeground(Notifications.ID_EXTENSION_INSTALLER, notification)
@@ -8,7 +8,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.tab.LocalTabNavigator
@@ -24,6 +23,8 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import eu.kanade.tachiyomi.ui.browse.source.sourcesTab
import eu.kanade.tachiyomi.ui.main.MainActivity
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.localize
data class BrowseTab(
private val toExtensions: Boolean = false,
@@ -36,7 +37,7 @@ data class BrowseTab(
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_browse_enter)
return TabOptions(
index = 3u,
title = stringResource(R.string.browse),
title = localize(MR.strings.browse),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
}
@@ -54,7 +55,7 @@ data class BrowseTab(
val extensionsState by extensionsScreenModel.state.collectAsState()
TabbedScreen(
titleRes = R.string.browse,
titleRes = MR.strings.browse,
tabs = persistentListOf(
sourcesTab(),
extensionsTab(extensionsScreenModel),
@@ -10,9 +10,9 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.ExtensionFilterScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.screens.LoadingScreen
class ExtensionFilterScreen : Screen() {
@@ -41,7 +41,7 @@ class ExtensionFilterScreen : Screen() {
screenModel.events.collectLatest {
when (it) {
ExtensionFilterEvent.FailedFetchingLanguages -> {
context.toast(R.string.internal_error)
context.localize(MR.strings.internal_error)
}
}
}
@@ -1,14 +1,13 @@
package eu.kanade.tachiyomi.ui.browse.extension
import android.app.Application
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.extension.interactor.GetExtensionsByType
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.components.SEARCH_DEBOUNCE_MILLIS
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep
@@ -28,6 +27,7 @@ import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import tachiyomi.core.util.lang.launchIO
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.seconds
@@ -86,13 +86,13 @@ class ExtensionsScreenModel(
val updates = _updates.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
if (updates.isNotEmpty()) {
itemsGroups[ExtensionUiModel.Header.Resource(R.string.ext_updates_pending)] = updates
itemsGroups[ExtensionUiModel.Header.Resource(MR.strings.ext_updates_pending)] = updates
}
val installed = _installed.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
val untrusted = _untrusted.filter(queryFilter(searchQuery)).map(extensionMapper(downloads))
if (installed.isNotEmpty() || untrusted.isNotEmpty()) {
itemsGroups[ExtensionUiModel.Header.Resource(R.string.ext_installed)] = installed + untrusted
itemsGroups[ExtensionUiModel.Header.Resource(MR.strings.ext_installed)] = installed + untrusted
}
val languagesWithExtensions = _available
@@ -209,7 +209,7 @@ typealias ItemGroups = MutableMap<ExtensionUiModel.Header, List<ExtensionUiModel
object ExtensionUiModel {
sealed interface Header {
data class Resource(@StringRes val textRes: Int) : Header
data class Resource(val textRes: StringResource) : Header
data class Text(val text: String) : Header
}
@@ -5,16 +5,16 @@ import androidx.compose.material.icons.outlined.Translate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.ExtensionScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.TabContent
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.localize
@Composable
fun extensionsTab(
@@ -24,12 +24,12 @@ fun extensionsTab(
val state by extensionsScreenModel.state.collectAsState()
return TabContent(
titleRes = R.string.label_extensions,
titleRes = MR.strings.label_extensions,
badgeNumber = state.updates.takeIf { it > 0 },
searchEnabled = true,
actions = persistentListOf(
AppBar.Action(
title = stringResource(R.string.action_filter),
title = localize(MR.strings.action_filter),
icon = Icons.Outlined.Translate,
onClick = { navigator.push(ExtensionFilterScreen()) },
),
@@ -1,20 +1,21 @@
package eu.kanade.tachiyomi.ui.browse.migration
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadCache
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
data class MigrationFlag(
val flag: Int,
val isDefaultSelected: Boolean,
val titleId: Int,
val titleId: StringResource,
) {
companion object {
fun create(flag: Int, defaultSelectionMap: Int, titleId: Int): MigrationFlag {
fun create(flag: Int, defaultSelectionMap: Int, titleId: StringResource): MigrationFlag {
return MigrationFlag(
flag = flag,
isDefaultSelected = defaultSelectionMap and flag != 0,
@@ -53,15 +54,15 @@ object MigrationFlags {
/** Returns information about applicable flags with default selections. */
fun getFlags(manga: Manga?, defaultSelectedBitMap: Int): List<MigrationFlag> {
val flags = mutableListOf<MigrationFlag>()
flags += MigrationFlag.create(CHAPTERS, defaultSelectedBitMap, R.string.chapters)
flags += MigrationFlag.create(CATEGORIES, defaultSelectedBitMap, R.string.categories)
flags += MigrationFlag.create(CHAPTERS, defaultSelectedBitMap, MR.strings.chapters)
flags += MigrationFlag.create(CATEGORIES, defaultSelectedBitMap, MR.strings.categories)
if (manga != null) {
if (manga.hasCustomCover(coverCache)) {
flags += MigrationFlag.create(CUSTOM_COVER, defaultSelectedBitMap, R.string.custom_cover)
flags += MigrationFlag.create(CUSTOM_COVER, defaultSelectedBitMap, MR.strings.custom_cover)
}
if (downloadCache.getDownloadCount(manga) > 0) {
flags += MigrationFlag.create(DELETE_DOWNLOADED, defaultSelectedBitMap, R.string.delete_downloaded)
flags += MigrationFlag.create(DELETE_DOWNLOADED, defaultSelectedBitMap, MR.strings.delete_downloaded)
}
}
return flags
@@ -10,11 +10,11 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.MigrateMangaScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.screens.LoadingScreen
data class MigrateMangaScreen(
@@ -46,7 +46,7 @@ data class MigrateMangaScreen(
screenModel.events.collectLatest { event ->
when (event) {
MigrationMangaEvent.FailedFetchingFavorites -> {
context.toast(R.string.internal_error)
context.toast(MR.strings.internal_error)
}
}
}
@@ -19,14 +19,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.model.StateScreenModel
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.hasCustomCover
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.track.EnhancedTracker
@@ -49,7 +47,9 @@ import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.i18n.localize
import tachiyomi.presentation.core.screens.LoadingScreen
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -79,7 +79,7 @@ internal fun MigrateDialog(
AlertDialog(
onDismissRequest = onDismissRequest,
title = {
Text(text = stringResource(R.string.migration_dialog_what_to_include))
Text(text = localize(MR.strings.migration_dialog_what_to_include))
},
text = {
Column(
@@ -87,7 +87,7 @@ internal fun MigrateDialog(
) {
flags.forEachIndexed { index, flag ->
LabeledCheckbox(
label = stringResource(flag.titleId),
label = localize(flag.titleId),
checked = selectedFlags[index],
onCheckedChange = { selectedFlags[index] = it },
)
@@ -104,7 +104,7 @@ internal fun MigrateDialog(
onClickTitle()
},
) {
Text(text = stringResource(R.string.action_show_manga))
Text(text = localize(MR.strings.action_show_manga))
}
Spacer(modifier = Modifier.weight(1f))
@@ -122,7 +122,7 @@ internal fun MigrateDialog(
}
},
) {
Text(text = stringResource(R.string.copy))
Text(text = localize(MR.strings.copy))
}
TextButton(
onClick = {
@@ -138,7 +138,7 @@ internal fun MigrateDialog(
}
},
) {
Text(text = stringResource(R.string.migrate))
Text(text = localize(MR.strings.migrate))
}
}
},
@@ -14,7 +14,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
@@ -22,7 +21,6 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.BrowseSourceContent
import eu.kanade.presentation.components.SearchToolbar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
import eu.kanade.tachiyomi.ui.browse.source.browse.SourceFilterDialog
@@ -32,8 +30,10 @@ import eu.kanade.tachiyomi.ui.webview.WebViewScreen
import kotlinx.coroutines.launch
import tachiyomi.core.Constants
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.localize
import tachiyomi.source.local.LocalSource
data class SourceSearchScreen(
@@ -66,7 +66,7 @@ data class SourceSearchScreen(
floatingActionButton = {
AnimatedVisibility(visible = state.filters.isNotEmpty()) {
ExtendedFloatingActionButton(
text = { Text(text = stringResource(R.string.action_filter)) },
text = { Text(text = localize(MR.strings.action_filter)) },
icon = { Icon(Icons.Outlined.FilterList, contentDescription = null) },
onClick = screenModel::openFilterSheet,
)
@@ -2,12 +2,10 @@ package eu.kanade.tachiyomi.ui.browse.migration.sources
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
@@ -15,9 +13,10 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.MigrateSourceScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.TabContent
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.migration.manga.MigrateMangaScreen
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.localize
@Composable
fun Screen.migrateSourceTab(): TabContent {
@@ -27,10 +26,10 @@ fun Screen.migrateSourceTab(): TabContent {
val state by screenModel.state.collectAsState()
return TabContent(
titleRes = R.string.label_migration,
titleRes = MR.strings.label_migration,
actions = persistentListOf(
AppBar.Action(
title = stringResource(R.string.migration_help_guide),
title = localize(MR.strings.migration_help_guide),
icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = {
uriHandler.openUri("https://tachiyomi.org/docs/guides/source-migration")
@@ -10,8 +10,8 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.browse.SourcesFilterScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.toast
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.screens.LoadingScreen
class SourcesFilterScreen : Screen() {
@@ -30,7 +30,7 @@ class SourcesFilterScreen : Screen() {
if (state is SourcesFilterScreenModel.State.Error) {
val context = LocalContext.current
LaunchedEffect(Unit) {
context.toast(R.string.internal_error)
context.toast(MR.strings.internal_error)
navigator.pop()
}
return
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
@@ -16,12 +15,13 @@ import eu.kanade.presentation.browse.SourceOptionsDialog
import eu.kanade.presentation.browse.SourcesScreen
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.TabContent
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.localize
@Composable
fun Screen.sourcesTab(): TabContent {
@@ -30,15 +30,15 @@ fun Screen.sourcesTab(): TabContent {
val state by screenModel.state.collectAsState()
return TabContent(
titleRes = R.string.label_sources,
titleRes = MR.strings.label_sources,
actions = persistentListOf(
AppBar.Action(
title = stringResource(R.string.action_global_search),
title = localize(MR.strings.action_global_search),
icon = Icons.Outlined.TravelExplore,
onClick = { navigator.push(GlobalSearchScreen()) },
),
AppBar.Action(
title = stringResource(R.string.action_filter),
title = localize(MR.strings.action_filter),
icon = Icons.Outlined.FilterList,
onClick = { navigator.push(SourcesFilterScreen()) },
),
@@ -70,7 +70,7 @@ fun Screen.sourcesTab(): TabContent {
)
}
val internalErrString = stringResource(R.string.internal_error)
val internalErrString = localize(MR.strings.internal_error)
LaunchedEffect(Unit) {
screenModel.events.collectLatest { event ->
when (event) {
@@ -31,7 +31,6 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.paging.compose.collectAsLazyPagingItems
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
@@ -44,7 +43,6 @@ import eu.kanade.presentation.category.components.ChangeCategoryDialog
import eu.kanade.presentation.manga.DuplicateMangaDialog
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
@@ -58,8 +56,10 @@ import kotlinx.coroutines.flow.receiveAsFlow
import tachiyomi.core.Constants
import tachiyomi.core.util.lang.launchIO
import tachiyomi.domain.source.model.StubSource
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.localize
import tachiyomi.source.local.LocalSource
data class BrowseSourceScreen(
@@ -150,7 +150,7 @@ data class BrowseSourceScreen(
)
},
label = {
Text(text = stringResource(R.string.popular))
Text(text = localize(MR.strings.popular))
},
)
if ((screenModel.source as CatalogueSource).supportsLatest) {
@@ -169,7 +169,7 @@ data class BrowseSourceScreen(
)
},
label = {
Text(text = stringResource(R.string.latest))
Text(text = localize(MR.strings.latest))
},
)
}
@@ -186,7 +186,7 @@ data class BrowseSourceScreen(
)
},
label = {
Text(text = stringResource(R.string.action_filter))
Text(text = localize(MR.strings.action_filter))
},
)
}
@@ -14,13 +14,12 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.AdaptiveSheet
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import tachiyomi.core.preference.TriState
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.CollapsibleBox
import tachiyomi.presentation.core.components.HeadingItem
@@ -29,6 +28,7 @@ import tachiyomi.presentation.core.components.SortItem
import tachiyomi.presentation.core.components.TextItem
import tachiyomi.presentation.core.components.TriStateItem
import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.i18n.localize
@Composable
fun SourceFilterDialog(
@@ -52,7 +52,7 @@ fun SourceFilterDialog(
) {
TextButton(onClick = onReset) {
Text(
text = stringResource(R.string.action_reset),
text = localize(MR.strings.action_reset),
style = LocalTextStyle.current.copy(
color = MaterialTheme.colorScheme.primary,
),
@@ -65,7 +65,7 @@ fun SourceFilterDialog(
onFilter()
onDismissRequest()
}) {
Text(stringResource(R.string.action_filter))
Text(localize(MR.strings.action_filter))
}
}
HorizontalDivider()
@@ -1,10 +1,9 @@
package eu.kanade.tachiyomi.ui.category
import androidx.annotation.StringRes
import androidx.compose.runtime.Immutable
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import eu.kanade.tachiyomi.R
import dev.icerock.moko.resources.StringResource
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.channels.Channel
@@ -18,6 +17,7 @@ import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.RenameCategory
import tachiyomi.domain.category.interactor.ReorderCategory
import tachiyomi.domain.category.model.Category
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -128,8 +128,8 @@ sealed interface CategoryDialog {
}
sealed interface CategoryEvent {
sealed class LocalizedMessage(@StringRes val stringRes: Int) : CategoryEvent
data object InternalError : LocalizedMessage(R.string.internal_error)
sealed class LocalizedMessage(val stringRes: StringResource) : CategoryEvent
data object InternalError : LocalizedMessage(MR.strings.internal_error)
}
sealed interface CategoryScreenState {
@@ -6,17 +6,17 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.localize
import tachiyomi.presentation.core.screens.LoadingScreen
class DeepLinkScreen(
@@ -35,7 +35,7 @@ class DeepLinkScreen(
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.action_search_hint),
title = localize(MR.strings.action_search_hint),
navigateUp = navigator::pop,
scrollBehavior = scrollBehavior,
)
@@ -13,7 +13,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Sort
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.outlined.Pause
import androidx.compose.material.icons.outlined.Sort
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -36,7 +35,6 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
@@ -53,13 +51,14 @@ import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.NestedMenuItem
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.DownloadListBinding
import kotlinx.collections.immutable.persistentListOf
import tachiyomi.core.util.lang.launchUI
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.Pill
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.localize
import tachiyomi.presentation.core.screens.EmptyScreen
import kotlin.math.roundToInt
@@ -105,7 +104,7 @@ object DownloadQueueScreen : Screen() {
titleContent = {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = stringResource(R.string.label_download_queue),
text = localize(MR.strings.label_download_queue),
maxLines = 1,
modifier = Modifier.weight(1f, false),
overflow = TextOverflow.Ellipsis,
@@ -132,10 +131,10 @@ object DownloadQueueScreen : Screen() {
onDismissRequest = onDismissRequest,
) {
NestedMenuItem(
text = { Text(text = stringResource(R.string.action_order_by_upload_date)) },
text = { Text(text = localize(MR.strings.action_order_by_upload_date)) },
children = { closeMenu ->
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_newest)) },
text = { Text(text = localize(MR.strings.action_newest)) },
onClick = {
screenModel.reorderQueue(
{ it.download.chapter.dateUpload },
@@ -145,7 +144,7 @@ object DownloadQueueScreen : Screen() {
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_oldest)) },
text = { Text(text = localize(MR.strings.action_oldest)) },
onClick = {
screenModel.reorderQueue(
{ it.download.chapter.dateUpload },
@@ -157,10 +156,10 @@ object DownloadQueueScreen : Screen() {
},
)
NestedMenuItem(
text = { Text(text = stringResource(R.string.action_order_by_chapter_number)) },
text = { Text(text = localize(MR.strings.action_order_by_chapter_number)) },
children = { closeMenu ->
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_asc)) },
text = { Text(text = localize(MR.strings.action_asc)) },
onClick = {
screenModel.reorderQueue(
{ it.download.chapter.chapterNumber },
@@ -170,7 +169,7 @@ object DownloadQueueScreen : Screen() {
},
)
DropdownMenuItem(
text = { Text(text = stringResource(R.string.action_desc)) },
text = { Text(text = localize(MR.strings.action_desc)) },
onClick = {
screenModel.reorderQueue(
{ it.download.chapter.chapterNumber },
@@ -186,12 +185,12 @@ object DownloadQueueScreen : Screen() {
AppBarActions(
persistentListOf(
AppBar.Action(
title = stringResource(R.string.action_sort),
title = localize(MR.strings.action_sort),
icon = Icons.AutoMirrored.Outlined.Sort,
onClick = { sortExpanded = true },
),
AppBar.OverflowAction(
title = stringResource(R.string.action_cancel_all),
title = localize(MR.strings.action_cancel_all),
onClick = { screenModel.clearQueue() },
),
),
@@ -211,11 +210,11 @@ object DownloadQueueScreen : Screen() {
ExtendedFloatingActionButton(
text = {
val id = if (isRunning) {
R.string.action_pause
MR.strings.action_pause
} else {
R.string.action_resume
MR.strings.action_resume
}
Text(text = stringResource(id))
Text(text = localize(id))
},
icon = {
val icon = if (isRunning) {
@@ -239,7 +238,7 @@ object DownloadQueueScreen : Screen() {
) { contentPadding ->
if (downloadList.isEmpty()) {
EmptyScreen(
textResource = R.string.information_no_downloads,
stringRes = MR.strings.information_no_downloads,
modifier = Modifier.padding(contentPadding),
)
return@Scaffold
@@ -10,7 +10,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
@@ -28,7 +27,10 @@ import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import tachiyomi.core.i18n.localize
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.localize
object HistoryTab : Tab {
@@ -43,7 +45,7 @@ object HistoryTab : Tab {
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_history_enter)
return TabOptions(
index = 2u,
title = stringResource(R.string.label_recent_manga),
title = localize(MR.strings.label_recent_manga),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
}
@@ -101,9 +103,9 @@ object HistoryTab : Tab {
screenModel.events.collectLatest { e ->
when (e) {
HistoryScreenModel.Event.InternalError ->
snackbarHostState.showSnackbar(context.getString(R.string.internal_error))
snackbarHostState.showSnackbar(context.localize(MR.strings.internal_error))
HistoryScreenModel.Event.HistoryCleared ->
snackbarHostState.showSnackbar(context.getString(R.string.clear_history_completed))
snackbarHostState.showSnackbar(context.localize(MR.strings.clear_history_completed))
is HistoryScreenModel.Event.OpenChapter -> openChapter(context, e.chapter)
}
}
@@ -121,7 +123,7 @@ object HistoryTab : Tab {
val intent = ReaderActivity.newIntent(context, chapter.mangaId, chapter.id)
context.startActivity(intent)
} else {
snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter))
snackbarHostState.showSnackbar(context.localize(MR.strings.no_next_chapter))
}
}
}
@@ -25,7 +25,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
@@ -37,7 +36,6 @@ import cafe.adriel.voyager.navigator.tab.TabNavigator
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.util.Screen
import eu.kanade.presentation.util.isTabletUi
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.BrowseTab
import eu.kanade.tachiyomi.ui.download.DownloadQueueScreen
import eu.kanade.tachiyomi.ui.history.HistoryTab
@@ -53,9 +51,11 @@ import kotlinx.coroutines.launch
import soup.compose.material.motion.animation.materialFadeThroughIn
import soup.compose.material.motion.animation.materialFadeThroughOut
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.NavigationBar
import tachiyomi.presentation.core.components.material.NavigationRail
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.localizePlural
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -241,8 +241,8 @@ object HomeScreen : Screen() {
}
if (count > 0) {
Badge {
val desc = pluralStringResource(
id = R.plurals.notification_chapters_generic,
val desc = localizePlural(
MR.plurals.notification_chapters_generic,
count = count,
count,
)
@@ -260,8 +260,8 @@ object HomeScreen : Screen() {
}
if (count > 0) {
Badge {
val desc = pluralStringResource(
id = R.plurals.update_check_notification_ext_updates,
val desc = localizePlural(
MR.plurals.update_check_notification_ext_updates,
count = count,
count,
)
@@ -7,7 +7,6 @@ import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.HelpOutline
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
@@ -21,7 +20,6 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.util.fastAll
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
@@ -49,11 +47,14 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.launchIO
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.localize
import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.EmptyScreenAction
import tachiyomi.presentation.core.screens.LoadingScreen
@@ -68,7 +69,7 @@ object LibraryTab : Tab {
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_library_enter)
return TabOptions(
index = 0u,
title = stringResource(R.string.label_library),
title = localize(MR.strings.label_library),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
}
@@ -94,11 +95,11 @@ object LibraryTab : Tab {
val started = LibraryUpdateJob.startNow(context, category)
scope.launch {
val msgRes = when {
!started -> R.string.update_already_running
category != null -> R.string.updating_category
else -> R.string.updating_library
!started -> MR.strings.update_already_running
category != null -> MR.strings.updating_category
else -> MR.strings.updating_library
}
snackbarHostState.showSnackbar(context.getString(msgRes))
snackbarHostState.showSnackbar(context.localize(msgRes))
}
started
}
@@ -106,8 +107,8 @@ object LibraryTab : Tab {
Scaffold(
topBar = { scrollBehavior ->
val title = state.getToolbarTitle(
defaultTitle = stringResource(R.string.label_library),
defaultCategoryTitle = stringResource(R.string.label_default),
defaultTitle = localize(MR.strings.label_library),
defaultCategoryTitle = localize(MR.strings.label_default),
page = screenModel.activeCategoryIndex,
)
val tabVisible = state.showCategoryTabs && state.categories.size > 1
@@ -127,7 +128,9 @@ object LibraryTab : Tab {
if (randomItem != null) {
navigator.push(MangaScreen(randomItem.libraryManga.manga.id))
} else {
snackbarHostState.showSnackbar(context.getString(R.string.information_no_entries_found))
snackbarHostState.showSnackbar(
context.localize(MR.strings.information_no_entries_found),
)
}
}
},
@@ -154,11 +157,11 @@ object LibraryTab : Tab {
state.searchQuery.isNullOrEmpty() && !state.hasActiveFilters && state.isLibraryEmpty -> {
val handler = LocalUriHandler.current
EmptyScreen(
textResource = R.string.information_empty_library,
stringRes = MR.strings.information_empty_library,
modifier = Modifier.padding(contentPadding),
actions = persistentListOf(
EmptyScreenAction(
stringResId = R.string.getting_started_guide,
stringRes = MR.strings.getting_started_guide,
icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") },
),
@@ -184,7 +187,7 @@ object LibraryTab : Tab {
ReaderActivity.newIntent(context, chapter.mangaId, chapter.id),
)
} else {
snackbarHostState.showSnackbar(context.getString(R.string.no_next_chapter))
snackbarHostState.showSnackbar(context.localize(MR.strings.no_next_chapter))
}
}
Unit
@@ -34,7 +34,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.animation.doOnEnd
import androidx.core.net.toUri
@@ -61,7 +60,6 @@ import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.Migrations
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@@ -93,7 +91,9 @@ import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.release.interactor.GetApplicationRelease
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.localize
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -257,15 +257,15 @@ class MainActivity : BaseActivity() {
if (showChangelog) {
AlertDialog(
onDismissRequest = { showChangelog = false },
title = { Text(text = stringResource(R.string.updated_version, BuildConfig.VERSION_NAME)) },
title = { Text(text = localize(MR.strings.updated_version, BuildConfig.VERSION_NAME)) },
dismissButton = {
TextButton(onClick = { openInBrowser(RELEASE_URL) }) {
Text(text = stringResource(R.string.whats_new))
Text(text = localize(MR.strings.whats_new))
}
},
confirmButton = {
TextButton(onClick = { showChangelog = false }) {
Text(text = stringResource(R.string.action_ok))
Text(text = localize(MR.strings.action_ok))
}
},
)
@@ -9,7 +9,6 @@ import coil.imageLoader
import coil.request.ImageRequest
import coil.size.Size
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.saver.Image
import eu.kanade.tachiyomi.data.saver.ImageSaver
@@ -20,12 +19,14 @@ import eu.kanade.tachiyomi.util.system.toShareIntent
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -51,13 +52,13 @@ class MangaCoverScreenModel(
try {
saveCoverInternal(context, temp = false)
snackbarHostState.showSnackbar(
context.getString(R.string.cover_saved),
context.localize(MR.strings.cover_saved),
withDismissAction = true,
)
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
snackbarHostState.showSnackbar(
context.getString(R.string.error_saving_cover),
context.localize(MR.strings.error_saving_cover),
withDismissAction = true,
)
}
@@ -74,7 +75,7 @@ class MangaCoverScreenModel(
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
snackbarHostState.showSnackbar(
context.getString(R.string.error_sharing_cover),
context.localize(MR.strings.error_sharing_cover),
withDismissAction = true,
)
}
@@ -145,7 +146,7 @@ class MangaCoverScreenModel(
private fun notifyCoverUpdated(context: Context) {
screenModelScope.launch {
snackbarHostState.showSnackbar(
context.getString(R.string.cover_updated),
context.localize(MR.strings.cover_updated),
withDismissAction = true,
)
}
@@ -154,7 +155,7 @@ class MangaCoverScreenModel(
private fun notifyFailedCoverUpdate(context: Context, e: Throwable) {
screenModelScope.launch {
snackbarHostState.showSnackbar(
context.getString(R.string.notification_cover_update_failed),
context.localize(MR.strings.notification_cover_update_failed),
withDismissAction = true,
)
logcat(LogPriority.ERROR, e)
@@ -37,7 +37,6 @@ import eu.kanade.presentation.manga.components.SetIntervalDialog
import eu.kanade.presentation.util.AssistContentScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.presentation.util.isTabletUi
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.isLocalOrStub
import eu.kanade.tachiyomi.source.online.HttpSource
@@ -54,10 +53,12 @@ import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.screens.LoadingScreen
class MangaScreen(
@@ -291,7 +292,7 @@ class MangaScreen(
context.startActivity(
Intent.createChooser(
intent,
context.getString(R.string.action_share),
context.localize(MR.strings.action_share),
),
)
}
@@ -25,7 +25,6 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.util.formattedMessage
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
@@ -50,6 +49,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.preference.CheckboxState
import tachiyomi.core.preference.TriState
import tachiyomi.core.preference.mapAsCheckboxState
@@ -78,6 +78,7 @@ import tachiyomi.domain.manga.model.applyFilter
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.i18n.MR
import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -281,8 +282,8 @@ class MangaScreenModel(
screenModelScope.launch {
if (!hasDownloads()) return@launch
val result = snackbarHostState.showSnackbar(
message = context.getString(R.string.delete_downloads_for_manga),
actionLabel = context.getString(R.string.action_delete),
message = context.localize(MR.strings.delete_downloads_for_manga),
actionLabel = context.localize(MR.strings.action_delete),
withDismissAction = true,
)
if (result == SnackbarResult.ActionPerformed) {
@@ -550,7 +551,7 @@ class MangaScreenModel(
}
} catch (e: Throwable) {
val message = if (e is NoChaptersException) {
context.getString(R.string.no_chapters_error)
context.localize(MR.strings.no_chapters_error)
} else {
logcat(LogPriority.ERROR, e)
with(context) { e.formattedMessage }
@@ -647,8 +648,8 @@ class MangaScreenModel(
}
screenModelScope.launch {
val result = snackbarHostState.showSnackbar(
message = context.getString(R.string.snack_add_to_library),
actionLabel = context.getString(R.string.action_add),
message = context.localize(MR.strings.snack_add_to_library),
actionLabel = context.localize(MR.strings.action_add),
withDismissAction = true,
)
if (result == SnackbarResult.ActionPerformed && !isFavorited) {
@@ -869,7 +870,7 @@ class MangaScreenModel(
if (applyToExisting) {
setMangaDefaultChapterFlags.awaitAll()
}
snackbarHostState.showSnackbar(message = context.getString(R.string.chapter_settings_updated))
snackbarHostState.showSnackbar(message = context.localize(MR.strings.chapter_settings_updated))
}
}
@@ -28,7 +28,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -39,6 +38,7 @@ import cafe.adriel.voyager.core.model.screenModelScope
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.track.interactor.RefreshTracks
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.ui.UiPreferences
@@ -49,7 +49,6 @@ import eu.kanade.presentation.track.TrackScoreSelector
import eu.kanade.presentation.track.TrackStatusSelector
import eu.kanade.presentation.track.TrackerSearch
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.track.DeletableTracker
import eu.kanade.tachiyomi.data.track.EnhancedTracker
import eu.kanade.tachiyomi.data.track.Tracker
@@ -66,6 +65,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext
@@ -75,9 +75,11 @@ import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.DeleteTrack
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.model.Track
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.LabeledCheckbox
import tachiyomi.presentation.core.components.material.AlertDialogContent
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.localize
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.time.Instant
@@ -210,7 +212,7 @@ data class TrackInfoDialogHomeScreen(
val matchResult = item.tracker.match(manga) ?: throw Exception()
item.tracker.register(matchResult, mangaId)
} catch (e: Exception) {
withUIContext { Injekt.get<Application>().toast(R.string.error_no_match) }
withUIContext { Injekt.get<Application>().toast(MR.strings.error_no_match) }
}
}
}
@@ -227,10 +229,10 @@ data class TrackInfoDialogHomeScreen(
}
withUIContext {
context.toast(
context.getString(
R.string.track_error,
context.localize(
MR.strings.track_error,
track!!.name,
e.message,
e.message ?: "",
),
)
}
@@ -286,7 +288,7 @@ private data class TrackStatusSelectorScreen(
private val tracker: Tracker,
) : StateScreenModel<Model.State>(State(track.status.toInt())) {
fun getSelections(): Map<Int, Int?> {
fun getSelections(): Map<Int, StringResource?> {
return tracker.getStatusList().associateWith { tracker.getStatus(it) }
}
@@ -500,9 +502,9 @@ private data class TrackDateSelectorScreen(
}
TrackDateSelector(
title = if (start) {
stringResource(R.string.track_started_reading_date)
localize(MR.strings.track_started_reading_date)
} else {
stringResource(R.string.track_finished_reading_date)
localize(MR.strings.track_finished_reading_date)
},
initialSelectedDateMillis = screenModel.initialSelection,
selectableDates = selectableDates,
@@ -575,7 +577,7 @@ private data class TrackDateRemoverScreen(
},
title = {
Text(
text = stringResource(R.string.track_remove_date_conf_title),
text = localize(MR.strings.track_remove_date_conf_title),
textAlign = TextAlign.Center,
)
},
@@ -583,9 +585,9 @@ private data class TrackDateRemoverScreen(
val serviceName = screenModel.getServiceName()
Text(
text = if (start) {
stringResource(R.string.track_remove_start_date_conf_text, serviceName)
localize(MR.strings.track_remove_start_date_conf_text, serviceName)
} else {
stringResource(R.string.track_remove_finish_date_conf_text, serviceName)
localize(MR.strings.track_remove_finish_date_conf_text, serviceName)
},
)
},
@@ -595,7 +597,7 @@ private data class TrackDateRemoverScreen(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small, Alignment.End),
) {
TextButton(onClick = navigator::pop) {
Text(text = stringResource(R.string.action_cancel))
Text(text = localize(MR.strings.action_cancel))
}
FilledTonalButton(
onClick = {
@@ -607,7 +609,7 @@ private data class TrackDateRemoverScreen(
contentColor = MaterialTheme.colorScheme.onErrorContainer,
),
) {
Text(text = stringResource(R.string.action_remove))
Text(text = localize(MR.strings.action_remove))
}
}
},
@@ -751,7 +753,7 @@ private data class TrackerRemoveScreen(
},
title = {
Text(
text = stringResource(R.string.track_delete_title, serviceName),
text = localize(MR.strings.track_delete_title, serviceName),
textAlign = TextAlign.Center,
)
},
@@ -760,12 +762,12 @@ private data class TrackerRemoveScreen(
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
text = stringResource(R.string.track_delete_text, serviceName),
text = localize(MR.strings.track_delete_text, serviceName),
)
if (screenModel.isDeletable()) {
LabeledCheckbox(
label = stringResource(R.string.track_delete_remote_text, serviceName),
label = localize(MR.strings.track_delete_remote_text, serviceName),
checked = removeRemoteTrack,
onCheckedChange = { removeRemoteTrack = it },
)
@@ -781,7 +783,7 @@ private data class TrackerRemoveScreen(
),
) {
TextButton(onClick = navigator::pop) {
Text(text = stringResource(R.string.action_cancel))
Text(text = localize(MR.strings.action_cancel))
}
FilledTonalButton(
onClick = {
@@ -794,7 +796,7 @@ private data class TrackerRemoveScreen(
contentColor = MaterialTheme.colorScheme.onErrorContainer,
),
) {
Text(text = stringResource(R.string.action_ok))
Text(text = localize(MR.strings.action_ok))
}
}
},
@@ -8,7 +8,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
@@ -34,6 +33,8 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import tachiyomi.core.util.lang.launchIO
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.localize
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -46,7 +47,7 @@ object MoreTab : Tab {
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_more_enter)
return TabOptions(
index = 4u,
title = stringResource(R.string.label_more),
title = localize(MR.strings.label_more),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
}
@@ -27,7 +27,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.graphics.ColorUtils
import androidx.core.net.toUri
@@ -85,11 +84,13 @@ import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.Constants
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -372,7 +373,7 @@ class ReaderActivity : BaseActivity() {
onClickCropBorder = {
val enabled = viewModel.toggleCropBorders()
menuToggleToast?.cancel()
menuToggleToast = toast(if (enabled) R.string.on else R.string.off)
menuToggleToast = toast(if (enabled) MR.strings.on else MR.strings.off)
},
onClickSettings = viewModel::openSettingsDialog,
)
@@ -399,7 +400,7 @@ class ReaderActivity : BaseActivity() {
verticalAlignment = Alignment.CenterVertically,
) {
CircularProgressIndicator()
Text(stringResource(R.string.loading))
Text(localize(MR.strings.loading))
}
},
)
@@ -536,7 +537,7 @@ class ReaderActivity : BaseActivity() {
private fun shareChapter() {
assistUrl?.let {
val intent = it.toUri().toShareIntent(this, type = "text/plain")
startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
startActivity(Intent.createChooser(intent, localize(MR.strings.action_share)))
}
}
@@ -683,9 +684,9 @@ class ReaderActivity : BaseActivity() {
val intent = uri.toShareIntent(
context = applicationContext,
message = getString(R.string.share_page_info, manga.title, chapter.name, page.number),
message = localize(MR.strings.share_page_info, manga.title, chapter.name, page.number),
)
startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
startActivity(Intent.createChooser(intent, localize(MR.strings.action_share)))
}
/**
@@ -695,7 +696,7 @@ class ReaderActivity : BaseActivity() {
private fun onSaveImageResult(result: ReaderViewModel.SaveImageResult) {
when (result) {
is ReaderViewModel.SaveImageResult.Success -> {
toast(R.string.picture_saved)
toast(MR.strings.picture_saved)
}
is ReaderViewModel.SaveImageResult.Error -> {
logcat(LogPriority.ERROR, result.error)
@@ -710,9 +711,9 @@ class ReaderActivity : BaseActivity() {
private fun onSetAsCoverResult(result: ReaderViewModel.SetAsCoverResult) {
toast(
when (result) {
Success -> R.string.cover_updated
AddToLibraryFirst -> R.string.notification_first_add_to_library
Error -> R.string.notification_cover_update_failed
Success -> MR.strings.cover_updated
AddToLibraryFirst -> MR.strings.notification_first_add_to_library
Error -> MR.strings.notification_cover_update_failed
},
)
}
@@ -13,6 +13,7 @@ import androidx.core.graphics.withTranslation
import androidx.core.view.isVisible
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
import eu.kanade.tachiyomi.ui.reader.viewer.navigation.DisabledNavigation
import tachiyomi.core.i18n.localize
import kotlin.math.abs
class ReaderNavigationOverlayView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
@@ -79,8 +80,8 @@ class ReaderNavigationOverlayView(context: Context, attributeSet: AttributeSet)
// Calculate center of rect height on screen
val y = height * (abs(rect.top - rect.bottom) / 2)
drawText(context.getString(region.type.nameRes), x, y, textBorderPaint)
drawText(context.getString(region.type.nameRes), x, y, textPaint)
drawText(context.localize(region.type.nameRes), x, y, textBorderPaint)
drawText(context.localize(region.type.nameRes), x, y, textPaint)
}
}
}
@@ -15,6 +15,8 @@ import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.getBitmapOrNull
import eu.kanade.tachiyomi.util.system.notificationBuilder
import eu.kanade.tachiyomi.util.system.notify
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
/**
* Class used to show BigPictureStyle notifications
@@ -56,8 +58,8 @@ class SaveImageNotifier(private val context: Context) {
fun onError(error: String?) {
// Create notification
with(notificationBuilder) {
setContentTitle(context.getString(R.string.download_notifier_title_error))
setContentText(error ?: context.getString(R.string.unknown_error))
setContentTitle(context.localize(MR.strings.download_notifier_title_error))
setContentText(error ?: context.localize(MR.strings.unknown_error))
setSmallIcon(android.R.drawable.ic_menu_report_image)
}
updateNotification()
@@ -65,7 +67,7 @@ class SaveImageNotifier(private val context: Context) {
private fun showCompleteNotification(uri: Uri, image: Bitmap?) {
with(notificationBuilder) {
setContentTitle(context.getString(R.string.picture_saved))
setContentTitle(context.localize(MR.strings.picture_saved))
setSmallIcon(R.drawable.ic_photo_24dp)
image?.let { setStyle(NotificationCompat.BigPictureStyle().bigPicture(it)) }
setLargeIcon(image)
@@ -78,13 +80,13 @@ class SaveImageNotifier(private val context: Context) {
// Share action
addAction(
R.drawable.ic_share_24dp,
context.getString(R.string.action_share),
context.localize(MR.strings.action_share),
NotificationReceiver.shareImagePendingBroadcast(context, uri.path!!, notificationId),
)
// Delete action
addAction(
R.drawable.ic_delete_24dp,
context.getString(R.string.action_delete),
context.localize(MR.strings.action_delete),
NotificationReceiver.deleteImagePendingBroadcast(context, uri.path!!, notificationId),
)
@@ -2,16 +2,17 @@ package eu.kanade.tachiyomi.ui.reader.loader
import android.content.Context
import com.github.junrar.exception.UnsupportedRarV5Exception
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadProvider
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.model.StubSource
import tachiyomi.i18n.MR
import tachiyomi.source.local.LocalSource
import tachiyomi.source.local.io.Format
@@ -46,7 +47,7 @@ class ChapterLoader(
.onEach { it.chapter = chapter }
if (pages.isEmpty()) {
throw Exception(context.getString(R.string.page_list_empty_error))
throw Exception(context.localize(MR.strings.page_list_empty_error))
}
// If the chapter is partially read, set the starting page to the last the user read
@@ -91,14 +92,14 @@ class ChapterLoader(
is Format.Rar -> try {
RarPageLoader(format.file)
} catch (e: UnsupportedRarV5Exception) {
error(context.getString(R.string.loader_rar5_error))
error(context.localize(MR.strings.loader_rar5_error))
}
is Format.Epub -> EpubPageLoader(format.file)
}
}
source is HttpSource -> HttpPageLoader(chapter, source)
source is StubSource -> error(context.getString(R.string.source_not_installed, source.toString()))
else -> error(context.getString(R.string.loader_not_implemented_error))
source is StubSource -> error(context.localize(MR.strings.source_not_installed, source.toString()))
else -> error(context.localize(MR.strings.loader_not_implemented_error))
}
}
}
@@ -2,54 +2,55 @@ package eu.kanade.tachiyomi.ui.reader.setting
import android.content.pm.ActivityInfo
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import tachiyomi.i18n.MR
enum class ReaderOrientation(
val flag: Int,
@StringRes val stringRes: Int,
val stringRes: StringResource,
@DrawableRes val iconRes: Int,
val flagValue: Int,
) {
DEFAULT(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
R.string.label_default,
MR.strings.label_default,
R.drawable.ic_screen_rotation_24dp,
0x00000000,
),
FREE(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED,
R.string.rotation_free,
MR.strings.rotation_free,
R.drawable.ic_screen_rotation_24dp,
0x00000008,
),
PORTRAIT(
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT,
R.string.rotation_portrait,
MR.strings.rotation_portrait,
R.drawable.ic_stay_current_portrait_24dp,
0x00000010,
),
LANDSCAPE(
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE,
R.string.rotation_landscape,
MR.strings.rotation_landscape,
R.drawable.ic_stay_current_landscape_24dp,
0x00000018,
),
LOCKED_PORTRAIT(
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
R.string.rotation_force_portrait,
MR.strings.rotation_force_portrait,
R.drawable.ic_screen_lock_portrait_24dp,
0x00000020,
),
LOCKED_LANDSCAPE(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
R.string.rotation_force_landscape,
MR.strings.rotation_force_landscape,
R.drawable.ic_screen_lock_landscape_24dp,
0x00000028,
),
REVERSE_PORTRAIT(
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
R.string.rotation_reverse_portrait,
MR.strings.rotation_reverse_portrait,
R.drawable.ic_stay_current_portrait_24dp,
0x00000030,
),
@@ -1,9 +1,9 @@
package eu.kanade.tachiyomi.ui.reader.setting
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import dev.icerock.moko.resources.StringResource
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.preference.getEnum
import tachiyomi.i18n.MR
class ReaderPreferences(
private val preferenceStore: PreferenceStore,
@@ -129,14 +129,14 @@ class ReaderPreferences(
// endregion
enum class TappingInvertMode(
@StringRes val titleResId: Int,
val titleRes: StringResource,
val shouldInvertHorizontal: Boolean = false,
val shouldInvertVertical: Boolean = false,
) {
NONE(R.string.tapping_inverted_none),
HORIZONTAL(R.string.tapping_inverted_horizontal, shouldInvertHorizontal = true),
VERTICAL(R.string.tapping_inverted_vertical, shouldInvertVertical = true),
BOTH(R.string.tapping_inverted_both, shouldInvertHorizontal = true, shouldInvertVertical = true),
NONE(MR.strings.tapping_inverted_none),
HORIZONTAL(MR.strings.tapping_inverted_horizontal, shouldInvertHorizontal = true),
VERTICAL(MR.strings.tapping_inverted_vertical, shouldInvertVertical = true),
BOTH(MR.strings.tapping_inverted_both, shouldInvertHorizontal = true, shouldInvertVertical = true),
}
enum class ReaderHideThreshold(val threshold: Int) {
@@ -151,28 +151,28 @@ class ReaderPreferences(
const val WEBTOON_PADDING_MAX = 25
val TapZones = listOf(
R.string.label_default,
R.string.l_nav,
R.string.kindlish_nav,
R.string.edge_nav,
R.string.right_and_left_nav,
R.string.disabled_nav,
MR.strings.label_default,
MR.strings.l_nav,
MR.strings.kindlish_nav,
MR.strings.edge_nav,
MR.strings.right_and_left_nav,
MR.strings.disabled_nav,
)
val ImageScaleType = listOf(
R.string.scale_type_fit_screen,
R.string.scale_type_stretch,
R.string.scale_type_fit_width,
R.string.scale_type_fit_height,
R.string.scale_type_original_size,
R.string.scale_type_smart_fit,
MR.strings.scale_type_fit_screen,
MR.strings.scale_type_stretch,
MR.strings.scale_type_fit_width,
MR.strings.scale_type_fit_height,
MR.strings.scale_type_original_size,
MR.strings.scale_type_smart_fit,
)
val ZoomStart = listOf(
R.string.zoom_start_automatic,
R.string.zoom_start_left,
R.string.zoom_start_right,
R.string.zoom_start_center,
MR.strings.zoom_start_automatic,
MR.strings.zoom_start_left,
MR.strings.zoom_start_right,
MR.strings.zoom_start_center,
)
}
}
@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.reader.setting
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
@@ -9,45 +9,46 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.L2RPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import tachiyomi.i18n.MR
enum class ReadingMode(
@StringRes val stringRes: Int,
val stringRes: StringResource,
@DrawableRes val iconRes: Int,
val flagValue: Int,
val direction: Direction? = null,
val type: ViewerType? = null,
) {
DEFAULT(R.string.label_default, R.drawable.ic_reader_default_24dp, 0x00000000),
DEFAULT(MR.strings.label_default, R.drawable.ic_reader_default_24dp, 0x00000000),
LEFT_TO_RIGHT(
R.string.left_to_right_viewer,
MR.strings.left_to_right_viewer,
R.drawable.ic_reader_ltr_24dp,
0x00000001,
Direction.Horizontal,
ViewerType.Pager,
),
RIGHT_TO_LEFT(
R.string.right_to_left_viewer,
MR.strings.right_to_left_viewer,
R.drawable.ic_reader_rtl_24dp,
0x00000002,
Direction.Horizontal,
ViewerType.Pager,
),
VERTICAL(
R.string.vertical_viewer,
MR.strings.vertical_viewer,
R.drawable.ic_reader_vertical_24dp,
0x00000003,
Direction.Vertical,
ViewerType.Pager,
),
WEBTOON(
R.string.webtoon_viewer,
MR.strings.webtoon_viewer,
R.drawable.ic_reader_webtoon_24dp,
0x00000004,
Direction.Vertical,
ViewerType.Webtoon,
),
CONTINUOUS_VERTICAL(
R.string.vertical_plus_viewer,
MR.strings.vertical_plus_viewer,
R.drawable.ic_reader_continuous_vertical_24dp,
0x00000005,
Direction.Vertical,
@@ -3,19 +3,19 @@ package eu.kanade.tachiyomi.ui.reader.viewer
import android.graphics.Color
import android.graphics.PointF
import android.graphics.RectF
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import dev.icerock.moko.resources.StringResource
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.lang.invert
import tachiyomi.i18n.MR
abstract class ViewerNavigation {
sealed class NavigationRegion(@StringRes val nameRes: Int, val color: Int) {
data object MENU : NavigationRegion(R.string.action_menu, Color.argb(0xCC, 0x95, 0x81, 0x8D))
data object PREV : NavigationRegion(R.string.nav_zone_prev, Color.argb(0xCC, 0xFF, 0x77, 0x33))
data object NEXT : NavigationRegion(R.string.nav_zone_next, Color.argb(0xCC, 0x84, 0xE2, 0x96))
data object LEFT : NavigationRegion(R.string.nav_zone_left, Color.argb(0xCC, 0x7D, 0x11, 0x28))
data object RIGHT : NavigationRegion(R.string.nav_zone_right, Color.argb(0xCC, 0xA6, 0xCF, 0xD5))
sealed class NavigationRegion(val nameRes: StringResource, val color: Int) {
data object MENU : NavigationRegion(MR.strings.action_menu, Color.argb(0xCC, 0x95, 0x81, 0x8D))
data object PREV : NavigationRegion(MR.strings.nav_zone_prev, Color.argb(0xCC, 0xFF, 0x77, 0x33))
data object NEXT : NavigationRegion(MR.strings.nav_zone_next, Color.argb(0xCC, 0x84, 0xE2, 0x96))
data object LEFT : NavigationRegion(MR.strings.nav_zone_left, Color.argb(0xCC, 0x7D, 0x11, 0x28))
data object RIGHT : NavigationRegion(MR.strings.nav_zone_right, Color.argb(0xCC, 0xA6, 0xCF, 0xD5))
}
data class Region(
@@ -10,7 +10,6 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.LinearLayout
import androidx.appcompat.widget.AppCompatTextView
import com.google.android.material.progressindicator.CircularProgressIndicator
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderButton
@@ -21,6 +20,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
/**
* View of the ViewPager that contains a chapter transition.
@@ -104,7 +105,7 @@ class PagerTransitionHolder(
val textView = AppCompatTextView(context).apply {
wrapContent()
setText(R.string.transition_pages_loading)
text = context.localize(MR.strings.transition_pages_loading)
}
pagesContainer.addView(progress)
@@ -117,13 +118,13 @@ class PagerTransitionHolder(
private fun setError(error: Throwable) {
val textView = AppCompatTextView(context).apply {
wrapContent()
text = context.getString(R.string.transition_pages_error, error.message)
text = context.localize(MR.strings.transition_pages_error, error.message ?: "")
}
val retryBtn = ReaderButton(context).apply {
viewer = this@PagerTransitionHolder.viewer
wrapContent()
setText(R.string.action_retry)
text = context.localize(MR.strings.action_retry)
setOnClickListener {
val toChapter = transition.to
if (toChapter != null) {
@@ -9,7 +9,6 @@ import androidx.appcompat.widget.AppCompatTextView
import androidx.core.view.isNotEmpty
import androidx.core.view.isVisible
import com.google.android.material.progressindicator.CircularProgressIndicator
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderTransitionView
@@ -18,6 +17,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
/**
* Holder of the webtoon viewer that contains a chapter transition.
@@ -106,7 +107,7 @@ class WebtoonTransitionHolder(
val textView = AppCompatTextView(context).apply {
wrapContent()
setText(R.string.transition_pages_loading)
text = context.localize(MR.strings.transition_pages_loading)
}
pagesContainer.addView(progress)
@@ -119,12 +120,12 @@ class WebtoonTransitionHolder(
private fun setError(error: Throwable, transition: ChapterTransition) {
val textView = AppCompatTextView(context).apply {
wrapContent()
text = context.getString(R.string.transition_pages_error, error.message)
text = context.localize(MR.strings.transition_pages_error, error.message ?: "")
}
val retryBtn = AppCompatButton(context).apply {
wrapContent()
setText(R.string.action_retry)
text = context.localize(MR.strings.action_retry)
setOnClickListener {
val toChapter = transition.to
if (toChapter != null) {
@@ -3,13 +3,14 @@ package eu.kanade.tachiyomi.ui.security
import android.os.Bundle
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
import logcat.LogPriority
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.system.logcat
import tachiyomi.i18n.MR
/**
* Blank activity with a BiometricPrompt.
@@ -19,7 +20,7 @@ class UnlockActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startAuthentication(
getString(R.string.unlock_app_title, getString(R.string.app_name)),
localize(MR.strings.unlock_app_title, localize(MR.strings.app_name)),
confirmationRequired = false,
callback = object : AuthenticatorUtil.AuthenticationCallback() {
override fun onAuthenticationError(
@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.stats
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
@@ -11,8 +10,9 @@ import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.more.stats.StatsScreenContent
import eu.kanade.presentation.more.stats.StatsScreenState
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.i18n.localize
import tachiyomi.presentation.core.screens.LoadingScreen
class StatsScreen : Screen() {
@@ -27,7 +27,7 @@ class StatsScreen : Screen() {
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = stringResource(R.string.label_stats),
title = localize(MR.strings.label_stats),
navigateUp = navigator::pop,
scrollBehavior = scrollBehavior,
)
@@ -9,7 +9,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
@@ -27,6 +26,9 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel.Event
import kotlinx.coroutines.flow.collectLatest
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.localize
object UpdatesTab : Tab {
@@ -37,7 +39,7 @@ object UpdatesTab : Tab {
val image = AnimatedImageVector.animatedVectorResource(R.drawable.anim_updates_enter)
return TabOptions(
index = 1u,
title = stringResource(R.string.label_recent_updates),
title = localize(MR.strings.label_recent_updates),
icon = rememberAnimatedVectorPainter(image, isSelected),
)
}
@@ -88,15 +90,15 @@ object UpdatesTab : Tab {
screenModel.events.collectLatest { event ->
when (event) {
Event.InternalError -> screenModel.snackbarHostState.showSnackbar(
context.getString(R.string.internal_error),
context.localize(MR.strings.internal_error),
)
is Event.LibraryUpdateTriggered -> {
val msg = if (event.started) {
R.string.updating_library
MR.strings.updating_library
} else {
R.string.update_already_running
MR.strings.update_already_running
}
screenModel.snackbarHostState.showSnackbar(context.getString(msg))
screenModel.snackbarHostState.showSnackbar(context.localize(msg))
}
}
}
@@ -20,6 +20,7 @@ import logcat.LogPriority
import okhttp3.HttpUrl.Companion.toHttpUrl
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
class WebViewActivity : BaseActivity() {
@@ -38,7 +39,7 @@ class WebViewActivity : BaseActivity() {
super.onCreate(savedInstanceState)
if (!WebViewUtil.supportsWebView(this)) {
toast(R.string.information_webview_required, Toast.LENGTH_LONG)
toast(MR.strings.information_webview_required, Toast.LENGTH_LONG)
finish()
return
}
@@ -1,7 +1,9 @@
package eu.kanade.tachiyomi.util.lang
import android.content.Context
import eu.kanade.tachiyomi.R
import tachiyomi.core.i18n.localize
import tachiyomi.core.i18n.localizePlural
import tachiyomi.i18n.MR
import java.text.DateFormat
import java.time.Instant
import java.time.LocalDateTime
@@ -61,9 +63,9 @@ fun Date.toRelativeString(
val days = difference.floorDiv(MILLISECONDS_IN_DAY).toInt()
return when {
difference < 0 -> dateFormat.format(this)
difference < MILLISECONDS_IN_DAY -> context.getString(R.string.relative_time_today)
difference < MILLISECONDS_IN_DAY.times(7) -> context.resources.getQuantityString(
R.plurals.relative_time,
difference < MILLISECONDS_IN_DAY -> context.localize(MR.strings.relative_time_today)
difference < MILLISECONDS_IN_DAY.times(7) -> context.localizePlural(
MR.plurals.relative_time,
days,
days,
)
@@ -10,8 +10,9 @@ import androidx.biometric.auth.AuthPromptCallback
import androidx.biometric.auth.startClass2BiometricOrCredentialAuthentication
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import eu.kanade.tachiyomi.R
import kotlinx.coroutines.suspendCancellableCoroutine
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
import kotlin.coroutines.resume
object AuthenticatorUtil {
@@ -48,7 +49,7 @@ object AuthenticatorUtil {
suspend fun FragmentActivity.authenticate(
title: String,
subtitle: String? = getString(R.string.confirm_lock_change),
subtitle: String? = localize(MR.strings.confirm_lock_change),
): Boolean = suspendCancellableCoroutine { cont ->
if (!isAuthenticationSupported()) {
cont.resume(true)
@@ -24,7 +24,9 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.lang.truncateCenter
import logcat.LogPriority
import rikka.sui.Sui
import tachiyomi.core.i18n.localize
import tachiyomi.core.util.system.logcat
import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@@ -45,11 +47,11 @@ fun Context.copyToClipboard(label: String, content: String) {
// Android 13 and higher shows a visual confirmation of copied contents
// https://developer.android.com/about/versions/13/features/copy-paste
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
toast(getString(R.string.copied_to_clipboard, content.truncateCenter(50)))
toast(localize(MR.strings.copied_to_clipboard, content.truncateCenter(50)))
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
toast(R.string.clipboard_copy_error)
toast(MR.strings.clipboard_copy_error)
}
}
@@ -6,7 +6,8 @@ import android.content.Intent
import android.net.Uri
import android.os.Build
import androidx.core.content.IntentCompat
import eu.kanade.tachiyomi.R
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
import java.io.Serializable
fun Uri.toShareIntent(context: Context, type: String = "image/*", message: String? = null): Intent {
@@ -27,7 +28,7 @@ fun Uri.toShareIntent(context: Context, type: String = "image/*", message: Strin
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
return Intent.createChooser(shareIntent, context.getString(R.string.action_share)).apply {
return Intent.createChooser(shareIntent, context.localize(MR.strings.action_share)).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK
}
}
@@ -2,8 +2,9 @@ package eu.kanade.tachiyomi.util.system
import android.content.Context
import androidx.core.os.LocaleListCompat
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel
import tachiyomi.core.i18n.localize
import tachiyomi.i18n.MR
import java.util.Locale
/**
@@ -29,10 +30,10 @@ object LocaleHelper {
*/
fun getSourceDisplayName(lang: String?, context: Context): String {
return when (lang) {
SourcesScreenModel.LAST_USED_KEY -> context.getString(R.string.last_used_source)
SourcesScreenModel.PINNED_KEY -> context.getString(R.string.pinned_sources)
"other" -> context.getString(R.string.other_source)
"all" -> context.getString(R.string.multi_lang)
SourcesScreenModel.LAST_USED_KEY -> context.localize(MR.strings.last_used_source)
SourcesScreenModel.PINNED_KEY -> context.localize(MR.strings.pinned_sources)
"other" -> context.localize(MR.strings.other_source)
"all" -> context.localize(MR.strings.multi_lang)
else -> getDisplayName(lang)
}
}