Merge branch 'master' into sync-part-final

This commit is contained in:
KaiserBh 2023-12-15 07:01:37 +11:00 committed by GitHub
commit 3532074a70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
121 changed files with 900 additions and 447 deletions

View File

@ -22,7 +22,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "eu.kanade.tachiyomi" applicationId = "eu.kanade.tachiyomi"
versionCode = 111 versionCode = 112
versionName = "0.14.7" versionName = "0.14.7"
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"") buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")

View File

@ -24,6 +24,8 @@ class BasePreferences(
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType) fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
enum class ExtensionInstaller(val titleRes: StringResource) { enum class ExtensionInstaller(val titleRes: StringResource) {
LEGACY(MR.strings.ext_installer_legacy), LEGACY(MR.strings.ext_installer_legacy),
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller), PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller),

View File

@ -22,8 +22,8 @@ import tachiyomi.domain.chapter.service.ChapterRecognition
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
import java.lang.Long.max import java.lang.Long.max
import java.time.Instant
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.Date
import java.util.TreeSet import java.util.TreeSet
class SyncChaptersWithSource( class SyncChaptersWithSource(
@ -83,7 +83,7 @@ class SyncChaptersWithSource(
} }
} }
val rightNow = Date().time val rightNow = Instant.now().toEpochMilli()
// Used to not set upload date of older chapters // Used to not set upload date of older chapters
// to a higher value than newer chapters // to a higher value than newer chapters

View File

@ -10,8 +10,8 @@ import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.time.Instant
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.Date
class UpdateManga( class UpdateManga(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
@ -46,14 +46,14 @@ class UpdateManga(
// Never refresh covers if the url is empty to avoid "losing" existing covers // Never refresh covers if the url is empty to avoid "losing" existing covers
remoteManga.thumbnail_url.isNullOrEmpty() -> null remoteManga.thumbnail_url.isNullOrEmpty() -> null
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null !manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
localManga.isLocal() -> Date().time localManga.isLocal() -> Instant.now().toEpochMilli()
localManga.hasCustomCover(coverCache) -> { localManga.hasCustomCover(coverCache) -> {
coverCache.deleteFromCache(localManga, false) coverCache.deleteFromCache(localManga, false)
null null
} }
else -> { else -> {
coverCache.deleteFromCache(localManga, false) coverCache.deleteFromCache(localManga, false)
Date().time Instant.now().toEpochMilli()
} }
} }
@ -87,16 +87,16 @@ class UpdateManga(
} }
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean { suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time)) return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Instant.now().toEpochMilli()))
} }
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean { suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Date().time)) return mangaRepository.update(MangaUpdate(id = mangaId, coverLastModified = Instant.now().toEpochMilli()))
} }
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean { suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
val dateAdded = when (favorite) { val dateAdded = when (favorite) {
true -> Date().time true -> Instant.now().toEpochMilli()
false -> 0 false -> 0
} }
return mangaRepository.update( return mangaRepository.update(

View File

@ -17,8 +17,7 @@ import tachiyomi.core.util.system.logcat
import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.domain.track.interactor.GetTracks
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.minutes import java.util.concurrent.TimeUnit
import kotlin.time.toJavaDuration
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) : class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
@ -63,7 +62,7 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>() val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
.setConstraints(constraints) .setConstraints(constraints)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5.minutes.toJavaDuration()) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
.addTag(TAG) .addTag(TAG)
.build() .build()

View File

@ -14,6 +14,11 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.GetApp
import androidx.compose.material.icons.outlined.Public
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.VerifiedUser
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
@ -62,6 +67,7 @@ fun ExtensionScreen(
searchQuery: String?, searchQuery: String?,
onLongClickItem: (Extension) -> Unit, onLongClickItem: (Extension) -> Unit,
onClickItemCancel: (Extension) -> Unit, onClickItemCancel: (Extension) -> Unit,
onClickItemWebView: (Extension.Available) -> Unit,
onInstallExtension: (Extension.Available) -> Unit, onInstallExtension: (Extension.Available) -> Unit,
onUninstallExtension: (Extension) -> Unit, onUninstallExtension: (Extension) -> Unit,
onUpdateExtension: (Extension.Installed) -> Unit, onUpdateExtension: (Extension.Installed) -> Unit,
@ -94,6 +100,7 @@ fun ExtensionScreen(
contentPadding = contentPadding, contentPadding = contentPadding,
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
onClickItemCancel = onClickItemCancel, onClickItemCancel = onClickItemCancel,
onClickItemWebView = onClickItemWebView,
onInstallExtension = onInstallExtension, onInstallExtension = onInstallExtension,
onUninstallExtension = onUninstallExtension, onUninstallExtension = onUninstallExtension,
onUpdateExtension = onUpdateExtension, onUpdateExtension = onUpdateExtension,
@ -111,6 +118,7 @@ private fun ExtensionContent(
state: ExtensionsScreenModel.State, state: ExtensionsScreenModel.State,
contentPadding: PaddingValues, contentPadding: PaddingValues,
onLongClickItem: (Extension) -> Unit, onLongClickItem: (Extension) -> Unit,
onClickItemWebView: (Extension.Available) -> Unit,
onClickItemCancel: (Extension) -> Unit, onClickItemCancel: (Extension) -> Unit,
onInstallExtension: (Extension.Available) -> Unit, onInstallExtension: (Extension.Available) -> Unit,
onUninstallExtension: (Extension) -> Unit, onUninstallExtension: (Extension) -> Unit,
@ -177,6 +185,7 @@ private fun ExtensionContent(
} }
}, },
onLongClickItem = onLongClickItem, onLongClickItem = onLongClickItem,
onClickItemWebView = onClickItemWebView,
onClickItemCancel = onClickItemCancel, onClickItemCancel = onClickItemCancel,
onClickItemAction = { onClickItemAction = {
when (it) { when (it) {
@ -217,6 +226,7 @@ private fun ExtensionItem(
item: ExtensionUiModel.Item, item: ExtensionUiModel.Item,
onClickItem: (Extension) -> Unit, onClickItem: (Extension) -> Unit,
onLongClickItem: (Extension) -> Unit, onLongClickItem: (Extension) -> Unit,
onClickItemWebView: (Extension.Available) -> Unit,
onClickItemCancel: (Extension) -> Unit, onClickItemCancel: (Extension) -> Unit,
onClickItemAction: (Extension) -> Unit, onClickItemAction: (Extension) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -260,6 +270,7 @@ private fun ExtensionItem(
ExtensionItemActions( ExtensionItemActions(
extension = extension, extension = extension,
installStep = installStep, installStep = installStep,
onClickItemWebView = onClickItemWebView,
onClickItemCancel = onClickItemCancel, onClickItemCancel = onClickItemCancel,
onClickItemAction = onClickItemAction, onClickItemAction = onClickItemAction,
) )
@ -343,42 +354,80 @@ private fun ExtensionItemActions(
extension: Extension, extension: Extension,
installStep: InstallStep, installStep: InstallStep,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClickItemWebView: (Extension.Available) -> Unit = {},
onClickItemCancel: (Extension) -> Unit = {}, onClickItemCancel: (Extension) -> Unit = {},
onClickItemAction: (Extension) -> Unit = {}, onClickItemAction: (Extension) -> Unit = {},
) { ) {
val isIdle = installStep.isCompleted() val isIdle = installStep.isCompleted()
Row(modifier = modifier) {
if (isIdle) { Row(
TextButton( modifier = modifier,
onClick = { onClickItemAction(extension) }, horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Text( when {
text = when (installStep) { !isIdle -> {
InstallStep.Installed -> stringResource(MR.strings.ext_installed) IconButton(onClick = { onClickItemCancel(extension) }) {
InstallStep.Error -> stringResource(MR.strings.action_retry) Icon(
InstallStep.Idle -> { imageVector = Icons.Outlined.Close,
when (extension) { contentDescription = stringResource(MR.strings.action_cancel),
is Extension.Installed -> { )
if (extension.hasUpdate) { }
stringResource(MR.strings.ext_update) }
} else { installStep == InstallStep.Error -> {
stringResource(MR.strings.action_settings) IconButton(onClick = { onClickItemAction(extension) }) {
} Icon(
} imageVector = Icons.Outlined.Refresh,
is Extension.Untrusted -> stringResource(MR.strings.ext_trust) contentDescription = stringResource(MR.strings.action_retry),
is Extension.Available -> stringResource(MR.strings.ext_install) )
}
}
installStep == InstallStep.Idle -> {
when (extension) {
is Extension.Installed -> {
if (extension.hasUpdate) {
IconButton(onClick = { onClickItemAction(extension) }) {
Icon(
imageVector = Icons.Outlined.GetApp,
contentDescription = stringResource(MR.strings.ext_update),
)
} }
} }
else -> error("Must not show install process text")
}, IconButton(onClick = { onClickItemAction(extension) }) {
) Icon(
} imageVector = Icons.Outlined.Settings,
} else { contentDescription = stringResource(MR.strings.action_settings),
IconButton(onClick = { onClickItemCancel(extension) }) { )
Icon( }
imageVector = Icons.Outlined.Close, }
contentDescription = stringResource(MR.strings.action_cancel), is Extension.Untrusted -> {
) IconButton(onClick = { onClickItemAction(extension) }) {
Icon(
imageVector = Icons.Outlined.VerifiedUser,
contentDescription = stringResource(MR.strings.ext_trust),
)
}
}
is Extension.Available -> {
if (extension.sources.isNotEmpty()) {
IconButton(
onClick = { onClickItemWebView(extension) },
) {
Icon(
imageVector = Icons.Outlined.Public,
contentDescription = stringResource(MR.strings.action_open_in_web_view),
)
}
}
IconButton(onClick = { onClickItemAction(extension) }) {
Icon(
imageVector = Icons.Outlined.GetApp,
contentDescription = stringResource(MR.strings.ext_install),
)
}
}
}
} }
} }
} }

View File

@ -2,7 +2,7 @@ package eu.kanade.presentation.crash
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.BugReport import androidx.compose.material.icons.outlined.BugReport
@ -47,7 +47,7 @@ fun CrashScreen(
modifier = Modifier modifier = Modifier
.padding(vertical = MaterialTheme.padding.small) .padding(vertical = MaterialTheme.padding.small)
.clip(MaterialTheme.shapes.small) .clip(MaterialTheme.shapes.small)
.fillMaxWidth() .fillMaxSize()
.background(MaterialTheme.colorScheme.surfaceVariant), .background(MaterialTheme.colorScheme.surfaceVariant),
) { ) {
Text( Text(

View File

@ -96,7 +96,7 @@ fun MangaScreen(
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?, onWebViewLongClicked: (() -> Unit)?,
onTrackingClicked: (() -> Unit)?, onTrackingClicked: () -> Unit,
// For tags menu // For tags menu
onTagSearch: (String) -> Unit, onTagSearch: (String) -> Unit,
@ -229,7 +229,7 @@ private fun MangaScreenSmallImpl(
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?, onWebViewLongClicked: (() -> Unit)?,
onTrackingClicked: (() -> Unit)?, onTrackingClicked: () -> Unit,
// For tags menu // For tags menu
onTagSearch: (String) -> Unit, onTagSearch: (String) -> Unit,
@ -481,7 +481,7 @@ fun MangaScreenLargeImpl(
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?, onWebViewLongClicked: (() -> Unit)?,
onTrackingClicked: (() -> Unit)?, onTrackingClicked: () -> Unit,
// For tags menu // For tags menu
onTagSearch: (String) -> Unit, onTagSearch: (String) -> Unit,

View File

@ -92,7 +92,6 @@ private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTIL
@Composable @Composable
fun MangaInfoBox( fun MangaInfoBox(
modifier: Modifier = Modifier,
isTabletUi: Boolean, isTabletUi: Boolean,
appBarPadding: Dp, appBarPadding: Dp,
title: String, title: String,
@ -104,6 +103,7 @@ fun MangaInfoBox(
status: Long, status: Long,
onCoverClick: () -> Unit, onCoverClick: () -> Unit,
doSearch: (query: String, global: Boolean) -> Unit, doSearch: (query: String, global: Boolean) -> Unit,
modifier: Modifier = Modifier,
) { ) {
Box(modifier = modifier) { Box(modifier = modifier) {
// Backdrop // Backdrop
@ -162,7 +162,6 @@ fun MangaInfoBox(
@Composable @Composable
fun MangaActionRow( fun MangaActionRow(
modifier: Modifier = Modifier,
favorite: Boolean, favorite: Boolean,
trackingCount: Int, trackingCount: Int,
fetchInterval: Int?, fetchInterval: Int?,
@ -170,9 +169,10 @@ fun MangaActionRow(
onAddToLibraryClicked: () -> Unit, onAddToLibraryClicked: () -> Unit,
onWebViewClicked: (() -> Unit)?, onWebViewClicked: (() -> Unit)?,
onWebViewLongClicked: (() -> Unit)?, onWebViewLongClicked: (() -> Unit)?,
onTrackingClicked: (() -> Unit)?, onTrackingClicked: () -> Unit,
onEditIntervalClicked: (() -> Unit)?, onEditIntervalClicked: (() -> Unit)?,
onEditCategory: (() -> Unit)?, onEditCategory: (() -> Unit)?,
modifier: Modifier = Modifier,
) { ) {
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f) val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
@ -200,18 +200,16 @@ fun MangaActionRow(
onClick = onEditIntervalClicked, onClick = onEditIntervalClicked,
) )
} }
if (onTrackingClicked != null) { MangaActionButton(
MangaActionButton( title = if (trackingCount == 0) {
title = if (trackingCount == 0) { stringResource(MR.strings.manga_tracking_tab)
stringResource(MR.strings.manga_tracking_tab) } else {
} else { pluralStringResource(MR.plurals.num_trackers, count = trackingCount, trackingCount)
pluralStringResource(MR.plurals.num_trackers, count = trackingCount, trackingCount) },
}, icon = if (trackingCount == 0) Icons.Outlined.Sync else Icons.Outlined.Done,
icon = if (trackingCount == 0) Icons.Outlined.Sync else Icons.Outlined.Done, color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary, onClick = onTrackingClicked,
onClick = onTrackingClicked, )
)
}
if (onWebViewClicked != null) { if (onWebViewClicked != null) {
MangaActionButton( MangaActionButton(
title = stringResource(MR.strings.action_web_view), title = stringResource(MR.strings.action_web_view),
@ -226,12 +224,12 @@ fun MangaActionRow(
@Composable @Composable
fun ExpandableMangaDescription( fun ExpandableMangaDescription(
modifier: Modifier = Modifier,
defaultExpandState: Boolean, defaultExpandState: Boolean,
description: String?, description: String?,
tagsProvider: () -> List<String>?, tagsProvider: () -> List<String>?,
onTagSearch: (String) -> Unit, onTagSearch: (String) -> Unit,
onCopyTagToClipboard: (tag: String) -> Unit, onCopyTagToClipboard: (tag: String) -> Unit,
modifier: Modifier = Modifier,
) { ) {
Column(modifier = modifier) { Column(modifier = modifier) {
val (expanded, onExpanded) = rememberSaveable { val (expanded, onExpanded) = rememberSaveable {
@ -406,13 +404,13 @@ private fun MangaAndSourceTitlesSmall(
@Composable @Composable
private fun MangaContentInfo( private fun MangaContentInfo(
title: String, title: String,
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
doSearch: (query: String, global: Boolean) -> Unit, doSearch: (query: String, global: Boolean) -> Unit,
author: String?, author: String?,
artist: String?, artist: String?,
status: Long, status: Long,
sourceName: String, sourceName: String,
isStubSource: Boolean, isStubSource: Boolean,
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
) { ) {
val context = LocalContext.current val context = LocalContext.current
Text( Text(
@ -556,7 +554,10 @@ private fun MangaSummary(
expanded: Boolean, expanded: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val animProgress by animateFloatAsState(if (expanded) 1f else 0f) val animProgress by animateFloatAsState(
targetValue = if (expanded) 1f else 0f,
label = "summary",
)
Layout( Layout(
modifier = modifier.clipToBounds(), modifier = modifier.clipToBounds(),
contents = listOf( contents = listOf(

View File

@ -0,0 +1,62 @@
package eu.kanade.presentation.more.onboarding
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.theme.TachiyomiTheme
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
@Composable
internal fun GuidesStep(
onRestoreBackup: () -> Unit,
) {
val handler = LocalUriHandler.current
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(stringResource(MR.strings.onboarding_guides_new_user, stringResource(MR.strings.app_name)))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = { handler.openUri(GETTING_STARTED_URL) },
) {
Text(stringResource(MR.strings.getting_started_guide))
}
HorizontalDivider(
color = MaterialTheme.colorScheme.onPrimaryContainer,
)
Text(stringResource(MR.strings.onboarding_guides_returning_user, stringResource(MR.strings.app_name)))
Button(
modifier = Modifier.fillMaxWidth(),
onClick = onRestoreBackup,
) {
Text(stringResource(MR.strings.pref_restore_backup))
}
}
}
const val GETTING_STARTED_URL = "https://tachiyomi.org/docs/guides/getting-started"
@PreviewLightDark
@Composable
private fun GuidesStepPreview() {
TachiyomiTheme {
GuidesStep(
onRestoreBackup = {},
)
}
}

View File

@ -0,0 +1,98 @@
package eu.kanade.presentation.more.onboarding
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.RocketLaunch
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.util.system.toast
import soup.compose.material.motion.animation.materialSharedAxisX
import soup.compose.material.motion.animation.rememberSlideDistance
import tachiyomi.domain.storage.service.StoragePreferences
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.InfoScreen
@Composable
fun OnboardingScreen(
storagePreferences: StoragePreferences,
uiPreferences: UiPreferences,
onComplete: () -> Unit,
onRestoreBackup: () -> Unit,
) {
val context = LocalContext.current
val slideDistance = rememberSlideDistance()
var currentStep by remember { mutableIntStateOf(0) }
val steps: List<@Composable () -> Unit> = remember {
listOf(
{ ThemeStep(uiPreferences = uiPreferences) },
{ StorageStep(storagePref = storagePreferences.baseStorageDirectory()) },
// TODO: prompt for notification permissions when bumping target to Android 13
{ GuidesStep(onRestoreBackup = onRestoreBackup) },
)
}
val isLastStep = currentStep == steps.size - 1
BackHandler(enabled = currentStep != 0, onBack = { currentStep-- })
InfoScreen(
icon = Icons.Outlined.RocketLaunch,
headingText = stringResource(MR.strings.onboarding_heading),
subtitleText = stringResource(MR.strings.onboarding_description),
acceptText = stringResource(
if (isLastStep) {
MR.strings.onboarding_action_finish
} else {
MR.strings.onboarding_action_next
},
),
onAcceptClick = {
if (isLastStep) {
onComplete()
} else {
// TODO: this is kind of janky
if (currentStep == 1 && !storagePreferences.baseStorageDirectory().isSet()) {
context.toast(MR.strings.onboarding_storage_selection_required)
} else {
currentStep++
}
}
},
) {
Box(
modifier = Modifier
.padding(vertical = MaterialTheme.padding.small)
.clip(MaterialTheme.shapes.small)
.fillMaxSize()
.background(MaterialTheme.colorScheme.surfaceVariant),
) {
AnimatedContent(
targetState = currentStep,
transitionSpec = {
materialSharedAxisX(
forward = targetState > initialState,
slideDistance = slideDistance,
)
},
label = "stepContent",
) {
steps[it]()
}
}
}
}

View File

@ -0,0 +1,52 @@
package eu.kanade.presentation.more.onboarding
import android.content.ActivityNotFoundException
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.more.settings.screen.SettingsDataScreen
import eu.kanade.tachiyomi.util.system.toast
import tachiyomi.core.preference.Preference
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.i18n.stringResource
@Composable
internal fun StorageStep(
storagePref: Preference<String>,
) {
val context = LocalContext.current
val pickStorageLocation = SettingsDataScreen.storageLocationPicker(storagePref)
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(
stringResource(
MR.strings.onboarding_storage_info,
stringResource(MR.strings.app_name),
SettingsDataScreen.storageLocationText(storagePref),
),
)
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
try {
pickStorageLocation.launch(null)
} catch (e: ActivityNotFoundException) {
context.toast(MR.strings.file_picker_error)
}
},
) {
Text(stringResource(MR.strings.onboarding_storage_action_select))
}
}
}

View File

@ -0,0 +1,40 @@
package eu.kanade.presentation.more.onboarding
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import tachiyomi.presentation.core.util.collectAsState
@Composable
internal fun ThemeStep(
uiPreferences: UiPreferences,
) {
val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme()
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState()
Column {
AppThemeModePreferenceWidget(
value = themeMode,
onItemClick = {
themeModePref.set(it)
setAppCompatDelegateThemeMode(it)
},
)
AppThemePreferenceWidget(
value = appTheme,
amoled = amoled,
onItemClick = { appThemePref.set(it) },
)
}
}

View File

@ -43,6 +43,7 @@ import eu.kanade.tachiyomi.network.PREF_DOH_NJALLA
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101 import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9 import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN import eu.kanade.tachiyomi.network.PREF_DOH_SHECAN
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.CrashLogUtil import eu.kanade.tachiyomi.util.CrashLogUtil
import eu.kanade.tachiyomi.util.system.isPreviewBuildType import eu.kanade.tachiyomi.util.system.isPreviewBuildType
import eu.kanade.tachiyomi.util.system.isReleaseBuildType import eu.kanade.tachiyomi.util.system.isReleaseBuildType
@ -110,6 +111,10 @@ object SettingsAdvancedScreen : SearchableSettings {
title = stringResource(MR.strings.pref_debug_info), title = stringResource(MR.strings.pref_debug_info),
onClick = { navigator.push(DebugInfoScreen()) }, onClick = { navigator.push(DebugInfoScreen()) },
), ),
Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_onboarding_guide),
onClick = { navigator.push(OnboardingScreen()) },
),
), ),
) )
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View File

@ -2,8 +2,8 @@ package eu.kanade.presentation.more.settings.screen
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.ReadOnlyComposable
@ -19,13 +19,11 @@ import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.AppThemeModePreferenceWidget
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.merge
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import tachiyomi.core.i18n.stringResource import tachiyomi.core.i18n.stringResource
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
@ -33,7 +31,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.Instant
object SettingsAppearanceScreen : SearchableSettings { object SettingsAppearanceScreen : SearchableSettings {
@ -43,72 +41,59 @@ object SettingsAppearanceScreen : SearchableSettings {
@Composable @Composable
override fun getPreferences(): List<Preference> { override fun getPreferences(): List<Preference> {
val context = LocalContext.current
val uiPreferences = remember { Injekt.get<UiPreferences>() } val uiPreferences = remember { Injekt.get<UiPreferences>() }
return listOf( return listOf(
getThemeGroup(context = context, uiPreferences = uiPreferences), getThemeGroup(uiPreferences = uiPreferences),
getDisplayGroup(context = context, uiPreferences = uiPreferences), getDisplayGroup(uiPreferences = uiPreferences),
) )
} }
@Composable @Composable
private fun getThemeGroup( private fun getThemeGroup(
context: Context,
uiPreferences: UiPreferences, uiPreferences: UiPreferences,
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val context = LocalContext.current
val themeModePref = uiPreferences.themeMode() val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState() val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme() val appThemePref = uiPreferences.appTheme()
val appTheme by appThemePref.collectAsState()
val amoledPref = uiPreferences.themeDarkAmoled() val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState() val amoled by amoledPref.collectAsState()
LaunchedEffect(themeMode) {
setAppCompatDelegateThemeMode(themeMode)
}
LaunchedEffect(Unit) {
merge(appThemePref.changes(), amoledPref.changes())
.drop(2)
.collectLatest { (context as? Activity)?.let { ActivityCompat.recreate(it) } }
}
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(MR.strings.pref_category_theme), title = stringResource(MR.strings.pref_category_theme),
preferenceItems = listOf( preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = themeModePref,
title = stringResource(MR.strings.pref_theme_mode),
entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mapOf(
ThemeMode.SYSTEM to stringResource(MR.strings.theme_system),
ThemeMode.LIGHT to stringResource(MR.strings.theme_light),
ThemeMode.DARK to stringResource(MR.strings.theme_dark),
)
} else {
mapOf(
ThemeMode.LIGHT to stringResource(MR.strings.theme_light),
ThemeMode.DARK to stringResource(MR.strings.theme_dark),
)
},
),
Preference.PreferenceItem.CustomPreference( Preference.PreferenceItem.CustomPreference(
title = stringResource(MR.strings.pref_app_theme), title = stringResource(MR.strings.pref_app_theme),
) { item -> ) {
val value by appThemePref.collectAsState() Column {
AppThemePreferenceWidget( AppThemeModePreferenceWidget(
title = item.title, value = themeMode,
value = value, onItemClick = {
amoled = amoled, themeModePref.set(it)
onItemClick = { appThemePref.set(it) }, setAppCompatDelegateThemeMode(it)
) },
)
AppThemePreferenceWidget(
value = appTheme,
amoled = amoled,
onItemClick = { appThemePref.set(it) },
)
}
}, },
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
pref = amoledPref, pref = amoledPref,
title = stringResource(MR.strings.pref_dark_theme_pure_black), title = stringResource(MR.strings.pref_dark_theme_pure_black),
enabled = themeMode != ThemeMode.LIGHT, enabled = themeMode != ThemeMode.LIGHT,
onValueChanged = {
(context as? Activity)?.let { ActivityCompat.recreate(it) }
true
},
), ),
), ),
) )
@ -116,14 +101,15 @@ object SettingsAppearanceScreen : SearchableSettings {
@Composable @Composable
private fun getDisplayGroup( private fun getDisplayGroup(
context: Context,
uiPreferences: UiPreferences, uiPreferences: UiPreferences,
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val context = LocalContext.current
val langs = remember { getLangs(context) } val langs = remember { getLangs(context) }
var currentLanguage by remember { var currentLanguage by remember {
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "") mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")
} }
val now = remember { Date().time } val now = remember { Instant.now().toEpochMilli() }
val dateFormat by uiPreferences.dateFormat().collectAsState() val dateFormat by uiPreferences.dateFormat().collectAsState()
val formattedNow = remember(dateFormat) { val formattedNow = remember(dateFormat) {

View File

@ -7,6 +7,7 @@ import android.net.Uri
import android.os.Environment import android.os.Environment
import android.text.format.Formatter import android.text.format.Formatter
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.ManagedActivityResultLauncher
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@ -89,13 +90,12 @@ object SettingsDataScreen : SearchableSettings {
} }
@Composable @Composable
private fun getStorageLocationPref( fun storageLocationPicker(
storagePreferences: StoragePreferences, storageDirPref: tachiyomi.core.preference.Preference<String>,
): Preference.PreferenceItem.TextPreference { ): ManagedActivityResultLauncher<Uri?, Uri?> {
val context = LocalContext.current val context = LocalContext.current
val storageDirPref = storagePreferences.baseStorageDirectory()
val storageDir by storageDirPref.collectAsState() return rememberLauncherForActivityResult(
val pickStorageLocation = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocumentTree(), contract = ActivityResultContracts.OpenDocumentTree(),
) { uri -> ) { uri ->
if (uri != null) { if (uri != null) {
@ -110,13 +110,35 @@ object SettingsDataScreen : SearchableSettings {
Injekt.get<DownloadCache>().invalidateCache() Injekt.get<DownloadCache>().invalidateCache()
} }
} }
}
@Composable
fun storageLocationText(
storageDirPref: tachiyomi.core.preference.Preference<String>,
): String {
val context = LocalContext.current
val storageDir by storageDirPref.collectAsState()
if (storageDir == storageDirPref.defaultValue()) {
return stringResource(MR.strings.no_location_set)
}
return remember(storageDir) {
val file = UniFile.fromUri(context, storageDir.toUri())
file?.filePath ?: file?.uri?.toString()
} ?: stringResource(MR.strings.invalid_location, storageDir)
}
@Composable
private fun getStorageLocationPref(
storagePreferences: StoragePreferences,
): Preference.PreferenceItem.TextPreference {
val context = LocalContext.current
val pickStorageLocation = storageLocationPicker(storagePreferences.baseStorageDirectory())
return Preference.PreferenceItem.TextPreference( return Preference.PreferenceItem.TextPreference(
title = stringResource(MR.strings.pref_storage_location), title = stringResource(MR.strings.pref_storage_location),
subtitle = remember(storageDir) { subtitle = storageLocationText(storagePreferences.baseStorageDirectory()),
val file = UniFile.fromUri(context, storageDir.toUri())
file?.filePath ?: file?.uri?.toString()
} ?: stringResource(MR.strings.invalid_location, storageDir),
onClick = { onClick = {
try { try {
pickStorageLocation.launch(null) pickStorageLocation.launch(null)

View File

@ -0,0 +1,56 @@
package eu.kanade.presentation.more.settings.widget
import android.os.Build
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MultiChoiceSegmentedButtonRow
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import eu.kanade.domain.ui.model.ThemeMode
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource
private val options = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mapOf(
ThemeMode.SYSTEM to MR.strings.theme_system,
ThemeMode.LIGHT to MR.strings.theme_light,
ThemeMode.DARK to MR.strings.theme_dark,
)
} else {
mapOf(
ThemeMode.LIGHT to MR.strings.theme_light,
ThemeMode.DARK to MR.strings.theme_dark,
)
}
@Composable
internal fun AppThemeModePreferenceWidget(
value: ThemeMode,
onItemClick: (ThemeMode) -> Unit,
) {
BasePreferenceWidget(
subcomponent = {
MultiChoiceSegmentedButtonRow(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = PrefsHorizontalPadding),
) {
options.onEachIndexed { index, (mode, labelRes) ->
SegmentedButton(
checked = mode == value,
onCheckedChange = { onItemClick(mode) },
shape = SegmentedButtonDefaults.itemShape(
index,
options.size,
),
) {
Text(stringResource(labelRes))
}
}
}
},
)
}

View File

@ -1,5 +1,6 @@
package eu.kanade.presentation.more.settings.widget package eu.kanade.presentation.more.settings.widget
import android.app.Activity
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -36,9 +37,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import eu.kanade.domain.ui.model.AppTheme import eu.kanade.domain.ui.model.AppTheme
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.theme.TachiyomiTheme
@ -51,13 +54,11 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
@Composable @Composable
internal fun AppThemePreferenceWidget( internal fun AppThemePreferenceWidget(
title: String,
value: AppTheme, value: AppTheme,
amoled: Boolean, amoled: Boolean,
onItemClick: (AppTheme) -> Unit, onItemClick: (AppTheme) -> Unit,
) { ) {
BasePreferenceWidget( BasePreferenceWidget(
title = title,
subcomponent = { subcomponent = {
AppThemesList( AppThemesList(
currentTheme = value, currentTheme = value,
@ -74,6 +75,7 @@ private fun AppThemesList(
amoled: Boolean, amoled: Boolean,
onItemClick: (AppTheme) -> Unit, onItemClick: (AppTheme) -> Unit,
) { ) {
val context = LocalContext.current
val appThemes = remember { val appThemes = remember {
AppTheme.entries AppTheme.entries
.filterNot { it.titleRes == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) } .filterNot { it.titleRes == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
@ -97,7 +99,10 @@ private fun AppThemesList(
) { ) {
AppThemePreviewItem( AppThemePreviewItem(
selected = currentTheme == appTheme, selected = currentTheme == appTheme,
onClick = { onItemClick(appTheme) }, onClick = {
onItemClick(appTheme)
(context as? Activity)?.let { ActivityCompat.recreate(it) }
},
) )
} }

View File

@ -7,7 +7,7 @@ import androidx.compose.runtime.ReadOnlyComposable
import tachiyomi.core.i18n.stringResource import tachiyomi.core.i18n.stringResource
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import java.util.Date import java.time.Instant
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
@ -29,7 +29,7 @@ fun Duration.toDurationString(context: Context, fallback: String): String {
@Composable @Composable
@ReadOnlyComposable @ReadOnlyComposable
fun relativeTimeSpanString(epochMillis: Long): String { fun relativeTimeSpanString(epochMillis: Long): String {
val now = Date().time val now = Instant.now().toEpochMilli()
return when { return when {
epochMillis <= 0L -> stringResource(MR.strings.relative_time_span_never) epochMillis <= 0L -> stringResource(MR.strings.relative_time_span_never)
now - epochMillis < 1.minutes.inWholeMilliseconds -> stringResource( now - epochMillis < 1.minutes.inWholeMilliseconds -> stringResource(

View File

@ -396,7 +396,12 @@ object Migrations {
newKey = { Preference.privateKey(it) }, newKey = { Preference.privateKey(it) },
) )
} }
if (oldVersion < 110) { if (oldVersion < 111) {
File(context.cacheDir, "dl_index_cache")
.takeIf { it.exists() }
?.delete()
}
if (oldVersion < 112) {
val prefsToReplace = listOf( val prefsToReplace = listOf(
"pref_download_only", "pref_download_only",
"incognito_mode", "incognito_mode",
@ -409,6 +414,7 @@ object Migrations {
"last_app_check", "last_app_check",
"last_ext_check", "last_ext_check",
"last_version_code", "last_version_code",
"storage_dir",
) )
replacePreferences( replacePreferences(
preferenceStore = preferenceStore, preferenceStore = preferenceStore,
@ -416,11 +422,6 @@ object Migrations {
newKey = { Preference.appStateKey(it) }, newKey = { Preference.appStateKey(it) },
) )
} }
if (oldVersion < 111) {
File(context.cacheDir, "dl_index_cache")
.takeIf { it.exists() }
?.delete()
}
return true return true
} }

View File

@ -25,10 +25,8 @@ import tachiyomi.domain.backup.service.BackupPreferences
import tachiyomi.domain.storage.service.StorageManager import tachiyomi.domain.storage.service.StorageManager
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.Instant
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.time.Duration.Companion.minutes
import kotlin.time.toJavaDuration
class BackupCreateJob(private val context: Context, workerParams: WorkerParameters) : class BackupCreateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
@ -52,7 +50,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
return try { return try {
val location = BackupCreator(context).createBackup(uri, flags, isAutoBackup) val location = BackupCreator(context).createBackup(uri, flags, isAutoBackup)
if (isAutoBackup) { if (isAutoBackup) {
backupPreferences.lastAutoBackupTimestamp().set(Date().time) backupPreferences.lastAutoBackupTimestamp().set(Instant.now().toEpochMilli())
} else { } else {
notifier.showBackupComplete(UniFile.fromUri(context, location.toUri())!!) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri())!!)
} }
@ -97,7 +95,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
10, 10,
TimeUnit.MINUTES, TimeUnit.MINUTES,
) )
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10.minutes.toJavaDuration()) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.MINUTES)
.addTag(TAG_AUTO) .addTag(TAG_AUTO)
.setConstraints(constraints) .setConstraints(constraints)
.setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true)) .setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true))

View File

@ -21,7 +21,7 @@ import eu.kanade.tachiyomi.source.sourcePreferences
import eu.kanade.tachiyomi.util.BackupUtil import eu.kanade.tachiyomi.util.BackupUtil
import eu.kanade.tachiyomi.util.system.createFileInCacheDir import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive import kotlinx.coroutines.ensureActive
import tachiyomi.core.i18n.stringResource import tachiyomi.core.i18n.stringResource
import tachiyomi.core.preference.AndroidPreferenceStore import tachiyomi.core.preference.AndroidPreferenceStore
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
@ -76,14 +76,12 @@ class BackupRestorer(
private val errors = mutableListOf<Pair<Date, String>>() private val errors = mutableListOf<Pair<Date, String>>()
suspend fun syncFromBackup(uri: Uri, sync: Boolean): Boolean { suspend fun syncFromBackup(uri: Uri, sync: Boolean) {
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
restoreProgress = 0 restoreProgress = 0
errors.clear() errors.clear()
if (!performRestore(uri, sync)) { performRestore(uri, sync)
return false
}
val endTime = System.currentTimeMillis() val endTime = System.currentTimeMillis()
val time = endTime - startTime val time = endTime - startTime
@ -95,7 +93,6 @@ class BackupRestorer(
} else { } else {
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name) notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
} }
return true
} }
private fun writeErrorLog(): File { private fun writeErrorLog(): File {
@ -117,74 +114,57 @@ class BackupRestorer(
return File("") return File("")
} }
private suspend fun performRestore(uri: Uri, sync: Boolean): Boolean { private suspend fun performRestore(uri: Uri, sync: Boolean) {
val backup = BackupUtil.decodeBackup(context, uri) val backup = BackupUtil.decodeBackup(context, uri)
restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs restoreAmount = backup.backupManga.size + 3 // +3 for categories, app prefs, source prefs
// Restore categories
if (backup.backupCategories.isNotEmpty()) {
restoreCategories(backup.backupCategories)
}
// Store source mapping for error messages // Store source mapping for error messages
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
sourceMapping = backupMaps.associate { it.sourceId to it.name } sourceMapping = backupMaps.associate { it.sourceId to it.name }
now = ZonedDateTime.now() now = ZonedDateTime.now()
currentFetchWindow = fetchInterval.getWindow(now) currentFetchWindow = fetchInterval.getWindow(now)
return coroutineScope { coroutineScope {
ensureActive()
restoreCategories(backup.backupCategories)
ensureActive()
restoreAppPreferences(backup.backupPreferences) restoreAppPreferences(backup.backupPreferences)
ensureActive()
restoreSourcePreferences(backup.backupSourcePreferences) restoreSourcePreferences(backup.backupSourcePreferences)
// Restore individual manga // Restore individual manga
backup.backupManga.forEach { backup.backupManga.forEach {
if (!isActive) { ensureActive()
return@coroutineScope false
}
restoreManga(it, backup.backupCategories, sync) restoreManga(it, backup.backupCategories, sync)
} }
// TODO: optionally trigger online library + tracker update
true // TODO: optionally trigger online library + tracker update
} }
} }
private suspend fun restoreCategories(backupCategories: List<BackupCategory>) { private suspend fun restoreCategories(backupCategories: List<BackupCategory>) {
// Get categories from file and from db if (backupCategories.isNotEmpty()) {
val dbCategories = getCategories.await() val dbCategories = getCategories.await()
val dbCategoriesByName = dbCategories.associateBy { it.name }
val categories = backupCategories.map { val categories = backupCategories.map {
var category = it.getCategory() dbCategoriesByName[it.name]
var found = false ?: handler.awaitOneExecutable {
for (dbCategory in dbCategories) { categoriesQueries.insert(it.name, it.order, it.flags)
// If the category is already in the db, assign the id to the file's category categoriesQueries.selectLastInsertedRowId()
// and do nothing }.let { id -> it.toCategory(id) }
if (category.name == dbCategory.name) {
category = category.copy(id = dbCategory.id)
found = true
break
}
}
if (!found) {
// Let the db assign the id
val id = handler.awaitOneExecutable {
categoriesQueries.insert(category.name, category.order, category.flags)
categoriesQueries.selectLastInsertedRowId()
}
category = category.copy(id = id)
} }
category libraryPreferences.categorizedDisplaySettings().set(
(dbCategories + categories)
.distinctBy { it.flags }
.size > 1,
)
} }
libraryPreferences.categorizedDisplaySettings().set(
(dbCategories + categories)
.distinctBy { it.flags }
.size > 1,
)
restoreProgress += 1 restoreProgress += 1
showRestoreProgress( showRestoreProgress(
restoreProgress, restoreProgress,

View File

@ -11,14 +11,12 @@ class BackupCategory(
// @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x // @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x
@ProtoNumber(100) var flags: Long = 0, @ProtoNumber(100) var flags: Long = 0,
) { ) {
fun getCategory(): Category { fun toCategory(id: Long) = Category(
return Category( id = id,
id = 0, name = this@BackupCategory.name,
name = this@BackupCategory.name, flags = this@BackupCategory.flags,
flags = this@BackupCategory.flags, order = this@BackupCategory.order,
order = this@BackupCategory.order, )
)
}
} }
val backupCategoryMapper = { category: Category -> val backupCategoryMapper = { category: Category ->

View File

@ -47,7 +47,6 @@ import tachiyomi.core.storage.extension
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNow import tachiyomi.core.util.lang.launchNow
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.ImageUtil import tachiyomi.core.util.system.ImageUtil
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
@ -265,24 +264,21 @@ class Downloader(
* @param chapters the list of chapters to download. * @param chapters the list of chapters to download.
* @param autoStart whether to start the downloader after enqueing the chapters. * @param autoStart whether to start the downloader after enqueing the chapters.
*/ */
fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) = launchIO { fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) {
if (chapters.isEmpty()) { if (chapters.isEmpty()) return
return@launchIO
}
val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchIO val source = sourceManager.get(manga.source) as? HttpSource ?: return
val wasEmpty = queueState.value.isEmpty() val wasEmpty = queueState.value.isEmpty()
val chaptersWithoutDir = chapters val chaptersToQueue = chapters.asSequence()
// Filter out those already downloaded. // Filter out those already downloaded.
.filter { provider.findChapterDir(it.name, it.scanlator, manga.title, source) == null } .filter { provider.findChapterDir(it.name, it.scanlator, manga.title, source) == null }
// Add chapters to queue from the start. // Add chapters to queue from the start.
.sortedByDescending { it.sourceOrder } .sortedByDescending { it.sourceOrder }
val chaptersToQueue = chaptersWithoutDir
// Filter out those already enqueued. // Filter out those already enqueued.
.filter { chapter -> queueState.value.none { it.chapter.id == chapter.id } } .filter { chapter -> queueState.value.none { it.chapter.id == chapter.id } }
// Create a download for each one. // Create a download for each one.
.map { Download(source, manga, it) } .map { Download(source, manga, it) }
.toList()
if (chaptersToQueue.isNotEmpty()) { if (chaptersToQueue.isNotEmpty()) {
addAllToQueue(chaptersToQueue) addAllToQueue(chaptersToQueue)
@ -299,13 +295,11 @@ class Downloader(
queuedDownloads > DOWNLOADS_QUEUED_WARNING_THRESHOLD || queuedDownloads > DOWNLOADS_QUEUED_WARNING_THRESHOLD ||
maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD
) { ) {
withUIContext { notifier.onWarning(
notifier.onWarning( context.stringResource(MR.strings.download_queue_size_warning),
context.stringResource(MR.strings.download_queue_size_warning), WARNING_NOTIF_TIMEOUT_MS,
WARNING_NOTIF_TIMEOUT_MS, NotificationHandler.openUrl(context, LibraryUpdateNotifier.HELP_WARNING_URL),
NotificationHandler.openUrl(context, LibraryUpdateNotifier.HELP_WARNING_URL), )
)
}
} }
DownloadJob.start(context) DownloadJob.start(context)
} }

View File

@ -69,8 +69,8 @@ import tachiyomi.i18n.MR
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.time.Instant
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.util.Date
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -111,7 +111,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
setForegroundSafely() setForegroundSafely()
libraryPreferences.lastUpdatedTimestamp().set(Date().time) libraryPreferences.lastUpdatedTimestamp().set(Instant.now().toEpochMilli())
val categoryId = inputData.getLong(KEY_CATEGORY, -1L) val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
addMangaToQueue(categoryId) addMangaToQueue(categoryId)

View File

@ -7,7 +7,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import androidx.core.net.toUri import androidx.core.net.toUri
import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
@ -15,7 +14,6 @@ import eu.kanade.tachiyomi.data.sync.SyncDataJob
import eu.kanade.tachiyomi.data.updater.AppUpdateDownloadJob import eu.kanade.tachiyomi.data.updater.AppUpdateDownloadJob
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.cancelNotification
import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
@ -66,12 +64,6 @@ class NotificationReceiver : BroadcastReceiver() {
context, context,
intent.getStringExtra(EXTRA_URI)!!.toUri(), intent.getStringExtra(EXTRA_URI)!!.toUri(),
) )
// Delete image from path and dismiss notification
ACTION_DELETE_IMAGE ->
deleteImage(
context,
intent.getStringExtra(EXTRA_URI)!!.toUri(),
)
// Share backup file // Share backup file
ACTION_SHARE_BACKUP -> ACTION_SHARE_BACKUP ->
shareFile( shareFile(
@ -172,16 +164,6 @@ class NotificationReceiver : BroadcastReceiver() {
} }
} }
/**
* Called to delete image
*
* @param uri path of file
*/
private fun deleteImage(context: Context, uri: Uri) {
UniFile.fromUri(context, uri)?.delete()
DiskUtil.scanMedia(context, uri)
}
/** /**
* Method called when user wants to stop a backup restore job. * Method called when user wants to stop a backup restore job.
* *
@ -429,26 +411,6 @@ class NotificationReceiver : BroadcastReceiver() {
) )
} }
/**
* Returns [PendingIntent] that starts a service which removes an image from disk
*
* @param context context of application
* @param uri location path of file
* @return [PendingIntent]
*/
internal fun deleteImagePendingBroadcast(context: Context, uri: Uri): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_DELETE_IMAGE
putExtra(EXTRA_URI, uri.toString())
}
return PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
}
/** /**
* Returns [PendingIntent] that starts a reader activity containing chapter. * Returns [PendingIntent] that starts a reader activity containing chapter.
* *

View File

@ -23,7 +23,7 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.util.Date import java.time.Instant
class ImageSaver( class ImageSaver(
val context: Context, val context: Context,
@ -79,7 +79,7 @@ class ImageSaver(
MediaStore.Images.Media.RELATIVE_PATH to relativePath, MediaStore.Images.Media.RELATIVE_PATH to relativePath,
MediaStore.Images.Media.DISPLAY_NAME to image.name, MediaStore.Images.Media.DISPLAY_NAME to image.name,
MediaStore.Images.Media.MIME_TYPE to type.mime, MediaStore.Images.Media.MIME_TYPE to type.mime,
MediaStore.Images.Media.DATE_MODIFIED to Date().time * 1000, MediaStore.Images.Media.DATE_MODIFIED to Instant.now().toEpochMilli(),
) )
val picture = findUriOrDefault(relativePath, filename) { val picture = findUriOrDefault(relativePath, filename) {

View File

@ -26,7 +26,10 @@ import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Calendar import java.time.Instant
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZonedDateTime
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
@ -328,13 +331,15 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
private fun parseDate(struct: JsonObject, dateKey: String): Long { private fun parseDate(struct: JsonObject, dateKey: String): Long {
return try { return try {
val date = Calendar.getInstance() return LocalDate
date.set( .of(
struct[dateKey]!!.jsonObject["year"]!!.jsonPrimitive.int, struct[dateKey]!!.jsonObject["year"]!!.jsonPrimitive.int,
struct[dateKey]!!.jsonObject["month"]!!.jsonPrimitive.int - 1, struct[dateKey]!!.jsonObject["month"]!!.jsonPrimitive.int,
struct[dateKey]!!.jsonObject["day"]!!.jsonPrimitive.int, struct[dateKey]!!.jsonObject["day"]!!.jsonPrimitive.int,
) )
date.timeInMillis .atStartOfDay(ZoneId.systemDefault())
.toInstant()
.toEpochMilli()
} catch (_: Exception) { } catch (_: Exception) {
0L 0L
} }
@ -349,12 +354,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
} }
} }
val calendar = Calendar.getInstance() val dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateValue), ZoneId.systemDefault())
calendar.timeInMillis = dateValue
return buildJsonObject { return buildJsonObject {
put("year", calendar.get(Calendar.YEAR)) put("year", dateTime.year)
put("month", calendar.get(Calendar.MONTH) + 1) put("month", dateTime.monthValue)
put("day", calendar.get(Calendar.DAY_OF_MONTH)) put("day", dateTime.dayOfMonth)
} }
} }

View File

@ -17,7 +17,7 @@ import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date import java.time.Instant
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days
internal class ExtensionGithubApi { internal class ExtensionGithubApi {
@ -76,14 +76,16 @@ internal class ExtensionGithubApi {
fromAvailableExtensionList: Boolean = false, fromAvailableExtensionList: Boolean = false,
): List<Extension.Installed>? { ): List<Extension.Installed>? {
// Limit checks to once a day at most // Limit checks to once a day at most
if (!fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) { if (!fromAvailableExtensionList &&
Instant.now().toEpochMilli() < lastExtCheck.get() + 1.days.inWholeMilliseconds
) {
return null return null
} }
val extensions = if (fromAvailableExtensionList) { val extensions = if (fromAvailableExtensionList) {
extensionManager.availableExtensionsFlow.value extensionManager.availableExtensionsFlow.value
} else { } else {
findExtensions().also { lastExtCheck.set(Date().time) } findExtensions().also { lastExtCheck.set(Instant.now().toEpochMilli()) }
} }
val installedExtensions = ExtensionLoader.loadExtensions(context) val installedExtensions = ExtensionLoader.loadExtensions(context)

View File

@ -12,6 +12,7 @@ import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.TabContent import eu.kanade.presentation.components.TabContent
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
@ -47,6 +48,17 @@ fun extensionsTab(
}, },
onClickItemCancel = extensionsScreenModel::cancelInstallUpdateExtension, onClickItemCancel = extensionsScreenModel::cancelInstallUpdateExtension,
onClickUpdateAll = extensionsScreenModel::updateAllExtensions, onClickUpdateAll = extensionsScreenModel::updateAllExtensions,
onClickItemWebView = { extension ->
extension.sources.getOrNull(0)?.let {
navigator.push(
WebViewScreen(
url = it.baseUrl,
initialTitle = it.name,
sourceId = it.id,
),
)
}
},
onInstallExtension = extensionsScreenModel::installExtension, onInstallExtension = extensionsScreenModel::installExtension,
onOpenExtension = { navigator.push(ExtensionDetailsScreen(it.pkgName)) }, onOpenExtension = { navigator.push(ExtensionDetailsScreen(it.pkgName)) },
onTrustExtension = { extensionsScreenModel.trustSignature(it.signatureHash) }, onTrustExtension = { extensionsScreenModel.trustSignature(it.signatureHash) },

View File

@ -53,7 +53,7 @@ import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.Instant
@Composable @Composable
internal fun MigrateDialog( internal fun MigrateDialog(
@ -298,7 +298,7 @@ internal class MigrateDialogScreenModel(
favorite = true, favorite = true,
chapterFlags = oldManga.chapterFlags, chapterFlags = oldManga.chapterFlags,
viewerFlags = oldManga.viewerFlags, viewerFlags = oldManga.viewerFlags,
dateAdded = if (replace) oldManga.dateAdded else Date().time, dateAdded = if (replace) oldManga.dateAdded else Instant.now().toEpochMilli(),
), ),
) )
} }

View File

@ -50,7 +50,7 @@ import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Date import java.time.Instant
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
class BrowseSourceScreenModel( class BrowseSourceScreenModel(
@ -225,7 +225,7 @@ class BrowseSourceScreenModel(
favorite = !manga.favorite, favorite = !manga.favorite,
dateAdded = when (manga.favorite) { dateAdded = when (manga.favorite) {
true -> 0 true -> 0
false -> Date().time false -> Instant.now().toEpochMilli()
}, },
) )

View File

@ -126,12 +126,12 @@ object HomeScreen : Screen() {
materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith
materialFadeThroughOut(durationMillis = TabFadeDuration) materialFadeThroughOut(durationMillis = TabFadeDuration)
}, },
content = { label = "tabContent",
tabNavigator.saveableState(key = "currentTab", it) { ) {
it.Content() tabNavigator.saveableState(key = "currentTab", it) {
} it.Content()
}, }
) }
} }
} }
} }

View File

@ -33,6 +33,7 @@ import eu.kanade.presentation.library.LibrarySettingsDialog
import eu.kanade.presentation.library.components.LibraryContent import eu.kanade.presentation.library.components.LibraryContent
import eu.kanade.presentation.library.components.LibraryToolbar import eu.kanade.presentation.library.components.LibraryToolbar
import eu.kanade.presentation.manga.components.LibraryBottomActionMenu import eu.kanade.presentation.manga.components.LibraryBottomActionMenu
import eu.kanade.presentation.more.onboarding.GETTING_STARTED_URL
import eu.kanade.presentation.util.Tab import eu.kanade.presentation.util.Tab
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
@ -163,7 +164,7 @@ object LibraryTab : Tab {
EmptyScreenAction( EmptyScreenAction(
stringRes = MR.strings.getting_started_guide, stringRes = MR.strings.getting_started_guide,
icon = Icons.AutoMirrored.Outlined.HelpOutline, icon = Icons.AutoMirrored.Outlined.HelpOutline,
onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") }, onClick = { handler.openUri(GETTING_STARTED_URL) },
), ),
), ),
) )

View File

@ -73,6 +73,7 @@ import eu.kanade.tachiyomi.ui.deeplink.DeepLinkScreen
import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
import eu.kanade.tachiyomi.ui.more.NewUpdateScreen import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
import eu.kanade.tachiyomi.ui.more.OnboardingScreen
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
import eu.kanade.tachiyomi.util.system.openInBrowser import eu.kanade.tachiyomi.util.system.openInBrowser
@ -251,6 +252,7 @@ class MainActivity : BaseActivity() {
HandleOnNewIntent(context = context, navigator = navigator) HandleOnNewIntent(context = context, navigator = navigator)
CheckForUpdates() CheckForUpdates()
ShowOnboarding()
} }
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) } var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
@ -342,6 +344,17 @@ class MainActivity : BaseActivity() {
} }
} }
@Composable
private fun ShowOnboarding() {
val navigator = LocalNavigator.currentOrThrow
LaunchedEffect(Unit) {
if (!preferences.shownOnboardingFlow().get()) {
navigator.push(OnboardingScreen())
}
}
}
/** /**
* Sets custom splash screen exit animation on devices prior to Android 12. * Sets custom splash screen exit animation on devices prior to Android 12.
* *

View File

@ -47,6 +47,7 @@ import eu.kanade.tachiyomi.ui.category.CategoryScreen
import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.home.HomeScreen
import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
import eu.kanade.tachiyomi.ui.webview.WebViewScreen import eu.kanade.tachiyomi.ui.webview.WebViewScreen
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toShareIntent
@ -130,7 +131,13 @@ class MangaScreen(
screenModel.source, screenModel.source,
) )
}.takeIf { isHttpSource }, }.takeIf { isHttpSource },
onTrackingClicked = screenModel::showTrackDialog.takeIf { successState.trackingAvailable }, onTrackingClicked = {
if (successState.trackingCount == 0) {
navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking))
} else {
screenModel.showTrackDialog()
}
},
onTagSearch = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } }, onTagSearch = { scope.launch { performGenreSearch(navigator, it, screenModel.source!!) } },
onFilterButtonClicked = screenModel::showSettingsDialog, onFilterButtonClicked = screenModel::showSettingsDialog,
onRefresh = screenModel::fetchAllFromSource, onRefresh = screenModel::fetchAllFromSource,

View File

@ -1090,9 +1090,6 @@ class MangaScreenModel(
val filterActive: Boolean val filterActive: Boolean
get() = scanlatorFilterActive || manga.chaptersFiltered() get() = scanlatorFilterActive || manga.chaptersFiltered()
val trackingAvailable: Boolean
get() = trackItems.isNotEmpty()
val trackingCount: Int val trackingCount: Int
get() = trackItems.count { it.track != null } get() = trackItems.count { it.track != null }

View File

@ -53,7 +53,7 @@ object MoreTab : Tab {
} }
override suspend fun onReselect(navigator: Navigator) { override suspend fun onReselect(navigator: Navigator) {
navigator.push(SettingsScreen.toMainScreen()) navigator.push(SettingsScreen())
} }
@Composable @Composable
@ -72,9 +72,9 @@ object MoreTab : Tab {
onClickDownloadQueue = { navigator.push(DownloadQueueScreen) }, onClickDownloadQueue = { navigator.push(DownloadQueueScreen) },
onClickCategories = { navigator.push(CategoryScreen()) }, onClickCategories = { navigator.push(CategoryScreen()) },
onClickStats = { navigator.push(StatsScreen()) }, onClickStats = { navigator.push(StatsScreen()) },
onClickDataAndStorage = { navigator.push(SettingsScreen.toDataAndStorageScreen()) }, onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) },
onClickSettings = { navigator.push(SettingsScreen.toMainScreen()) }, onClickSettings = { navigator.push(SettingsScreen()) },
onClickAbout = { navigator.push(SettingsScreen.toAboutScreen()) }, onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) },
) )
} }
} }

View File

@ -0,0 +1,41 @@
package eu.kanade.tachiyomi.ui.more
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.more.onboarding.OnboardingScreen
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.ui.setting.SettingsScreen
import tachiyomi.domain.storage.service.StoragePreferences
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class OnboardingScreen : Screen() {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val basePreferences = remember { Injekt.get<BasePreferences>() }
val storagePreferences = remember { Injekt.get<StoragePreferences>() }
val uiPreferences = remember { Injekt.get<UiPreferences>() }
val finishOnboarding = {
basePreferences.shownOnboardingFlow().set(true)
navigator.pop()
}
OnboardingScreen(
storagePreferences = storagePreferences,
uiPreferences = uiPreferences,
onComplete = { finishOnboarding() },
onRestoreBackup = {
finishOnboarding()
navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage))
},
)
}
}

View File

@ -74,6 +74,7 @@ import tachiyomi.domain.source.service.SourceManager
import tachiyomi.source.local.isLocal import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.time.Instant
import java.util.Date import java.util.Date
/** /**
@ -539,7 +540,7 @@ class ReaderViewModel @JvmOverloads constructor(
} }
fun restartReadTimer() { fun restartReadTimer() {
chapterReadStartTime = Date().time chapterReadStartTime = Instant.now().toEpochMilli()
} }
fun flushReadTimer() { fun flushReadTimer() {

View File

@ -83,12 +83,6 @@ class SaveImageNotifier(private val context: Context) {
context.stringResource(MR.strings.action_share), context.stringResource(MR.strings.action_share),
NotificationReceiver.shareImagePendingBroadcast(context, uri), NotificationReceiver.shareImagePendingBroadcast(context, uri),
) )
// Delete action
addAction(
R.drawable.ic_delete_24dp,
context.stringResource(MR.strings.action_delete),
NotificationReceiver.deleteImagePendingBroadcast(context, uri),
)
updateNotification() updateNotification()
} }

View File

@ -16,6 +16,7 @@ import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.source.local.isLocal
class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
AbstractComposeView(context, attrs) { AbstractComposeView(context, attrs) {
@ -31,7 +32,7 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
Data( Data(
transition = transition, transition = transition,
currChapterDownloaded = transition.from.pageLoader?.isLocal == true, currChapterDownloaded = transition.from.pageLoader?.isLocal == true,
goingToChapterDownloaded = transition.to?.chapter?.let { goingToChapter -> goingToChapterDownloaded = manga.isLocal() || transition.to?.chapter?.let { goingToChapter ->
downloadManager.isChapterDownloaded( downloadManager.isChapterDownloaded(
chapterName = goingToChapter.name, chapterName = goingToChapter.name,
chapterScanlator = goingToChapter.scanlator, chapterScanlator = goingToChapter.scanlator,

View File

@ -15,6 +15,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.screen.SettingsAppearanceScreen import eu.kanade.presentation.more.settings.screen.SettingsAppearanceScreen
import eu.kanade.presentation.more.settings.screen.SettingsDataScreen import eu.kanade.presentation.more.settings.screen.SettingsDataScreen
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
import eu.kanade.presentation.more.settings.screen.SettingsTrackingScreen
import eu.kanade.presentation.more.settings.screen.about.AboutScreen import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
import eu.kanade.presentation.util.LocalBackPress import eu.kanade.presentation.util.LocalBackPress
@ -22,22 +23,22 @@ import eu.kanade.presentation.util.Screen
import eu.kanade.presentation.util.isTabletUi import eu.kanade.presentation.util.isTabletUi
import tachiyomi.presentation.core.components.TwoPanelBox import tachiyomi.presentation.core.components.TwoPanelBox
class SettingsScreen private constructor( class SettingsScreen(
val toDataAndStorage: Boolean, private val destination: Int? = null,
val toAbout: Boolean,
) : Screen() { ) : Screen() {
constructor(destination: Destination) : this(destination.id)
@Composable @Composable
override fun Content() { override fun Content() {
val parentNavigator = LocalNavigator.currentOrThrow val parentNavigator = LocalNavigator.currentOrThrow
if (!isTabletUi()) { if (!isTabletUi()) {
Navigator( Navigator(
screen = if (toDataAndStorage) { screen = when (destination) {
SettingsDataScreen Destination.About.id -> AboutScreen
} else if (toAbout) { Destination.DataAndStorage.id -> SettingsDataScreen
AboutScreen Destination.Tracking.id -> SettingsTrackingScreen
} else { else -> SettingsMainScreen
SettingsMainScreen
}, },
content = { content = {
val pop: () -> Unit = { val pop: () -> Unit = {
@ -54,12 +55,11 @@ class SettingsScreen private constructor(
) )
} else { } else {
Navigator( Navigator(
screen = if (toDataAndStorage) { screen = when (destination) {
SettingsDataScreen Destination.About.id -> AboutScreen
} else if (toAbout) { Destination.DataAndStorage.id -> SettingsDataScreen
AboutScreen Destination.Tracking.id -> SettingsTrackingScreen
} else { else -> SettingsAppearanceScreen
SettingsAppearanceScreen
}, },
) { ) {
val insets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal) val insets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)
@ -78,11 +78,9 @@ class SettingsScreen private constructor(
} }
} }
companion object { sealed class Destination(val id: Int) {
fun toMainScreen() = SettingsScreen(toDataAndStorage = false, toAbout = false) data object About : Destination(0)
data object DataAndStorage : Destination(1)
fun toDataAndStorageScreen() = SettingsScreen(toDataAndStorage = true, toAbout = false) data object Tracking : Destination(2)
fun toAboutScreen() = SettingsScreen(toDataAndStorage = false, toAbout = true)
} }
} }

View File

@ -49,7 +49,7 @@ import tachiyomi.domain.updates.interactor.GetUpdates
import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.model.UpdatesWithRelations
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.Calendar import java.time.ZonedDateTime
import java.util.Date import java.util.Date
class UpdatesScreenModel( class UpdatesScreenModel(
@ -79,13 +79,10 @@ class UpdatesScreenModel(
init { init {
screenModelScope.launchIO { screenModelScope.launchIO {
// Set date limit for recent chapters // Set date limit for recent chapters
val calendar = Calendar.getInstance().apply { val limit = ZonedDateTime.now().minusMonths(3).toInstant()
time = Date()
add(Calendar.MONTH, -3)
}
combine( combine(
getUpdates.subscribe(calendar).distinctUntilChanged(), getUpdates.subscribe(limit).distinctUntilChanged(),
downloadCache.changes, downloadCache.changes,
downloadManager.queueState, downloadManager.queueState,
) { updates, _, _ -> updates } ) { updates, _, _ -> updates }

View File

@ -12,7 +12,7 @@ import tachiyomi.source.local.isLocal
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.InputStream import java.io.InputStream
import java.util.Date import java.time.Instant
/** /**
* Call before updating [Manga.thumbnail_url] to ensure old cover can be cleared from cache * Call before updating [Manga.thumbnail_url] to ensure old cover can be cleared from cache
@ -28,7 +28,7 @@ fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSa
return when { return when {
isLocal() -> { isLocal() -> {
this.copy(coverLastModified = Date().time) this.copy(coverLastModified = Instant.now().toEpochMilli())
} }
hasCustomCover(coverCache) -> { hasCustomCover(coverCache) -> {
coverCache.deleteFromCache(this, false) coverCache.deleteFromCache(this, false)
@ -36,7 +36,7 @@ fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSa
} }
else -> { else -> {
coverCache.deleteFromCache(this, false) coverCache.deleteFromCache(this, false)
this.copy(coverLastModified = Date().time) this.copy(coverLastModified = Instant.now().toEpochMilli())
} }
} }
} }
@ -44,7 +44,7 @@ fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSa
fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Manga { fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Manga {
if (isLocal()) return this if (isLocal()) return this
return if (coverCache.deleteFromCache(this, true) > 0) { return if (coverCache.deleteFromCache(this, true) > 0) {
return copy(coverLastModified = Date().time) return copy(coverLastModified = Instant.now().toEpochMilli())
} else { } else {
this this
} }

View File

@ -8,6 +8,7 @@ import java.text.DateFormat
import java.time.Instant import java.time.Instant
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.temporal.ChronoUnit
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
@ -38,13 +39,8 @@ fun Long.convertEpochMillisZone(
* @return date as time key * @return date as time key
*/ */
fun Long.toDateKey(): Date { fun Long.toDateKey(): Date {
val cal = Calendar.getInstance() val instant = Instant.ofEpochMilli(this)
cal.time = Date(this) return Date.from(instant.truncatedTo(ChronoUnit.DAYS))
cal[Calendar.HOUR_OF_DAY] = 0
cal[Calendar.MINUTE] = 0
cal[Calendar.SECOND] = 0
cal[Calendar.MILLISECOND] = 0
return cal.time
} }
private const val MILLISECONDS_IN_DAY = 86_400_000L private const val MILLISECONDS_IN_DAY = 86_400_000L

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFF"
android:pathData="M16,9v10H8V9h8m-1.5,-6h-5l-1,1H5v2h14V4h-3.5l-1,-1zM18,7H6v12c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7z" />
</vector>

View File

@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.stateIn
* Local-copy implementation of PreferenceStore mostly for test and preview purposes * Local-copy implementation of PreferenceStore mostly for test and preview purposes
*/ */
class InMemoryPreferenceStore( class InMemoryPreferenceStore(
private val initialPreferences: Sequence<InMemoryPreference<*>> = sequenceOf(), initialPreferences: Sequence<InMemoryPreference<*>> = sequenceOf(),
) : PreferenceStore { ) : PreferenceStore {
private val preferences: Map<String, Preference<*>> = private val preferences: Map<String, Preference<*>> =

View File

@ -1,5 +1,6 @@
package tachiyomi.domain.storage.service package tachiyomi.domain.storage.service
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.storage.FolderProvider import tachiyomi.core.storage.FolderProvider
@ -8,5 +9,5 @@ class StoragePreferences(
private val preferenceStore: PreferenceStore, private val preferenceStore: PreferenceStore,
) { ) {
fun baseStorageDirectory() = preferenceStore.getString("storage_dir", folderProvider.path()) fun baseStorageDirectory() = preferenceStore.getString(Preference.appStateKey("storage_dir"), folderProvider.path())
} }

View File

@ -3,7 +3,7 @@ package tachiyomi.domain.updates.interactor
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import tachiyomi.domain.updates.model.UpdatesWithRelations import tachiyomi.domain.updates.model.UpdatesWithRelations
import tachiyomi.domain.updates.repository.UpdatesRepository import tachiyomi.domain.updates.repository.UpdatesRepository
import java.util.Calendar import java.time.Instant
class GetUpdates( class GetUpdates(
private val repository: UpdatesRepository, private val repository: UpdatesRepository,
@ -13,8 +13,8 @@ class GetUpdates(
return repository.awaitWithRead(read, after, limit = 500) return repository.awaitWithRead(read, after, limit = 500)
} }
fun subscribe(calendar: Calendar): Flow<List<UpdatesWithRelations>> { fun subscribe(instant: Instant): Flow<List<UpdatesWithRelations>> {
return repository.subscribeAll(calendar.time.time, limit = 500) return repository.subscribeAll(instant.toEpochMilli(), limit = 500)
} }
fun subscribe(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> { fun subscribe(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {

View File

@ -1,5 +1,5 @@
[versions] [versions]
compiler = "1.5.5" compiler = "1.5.6"
compose-bom = "2023.12.00-alpha03" compose-bom = "2023.12.00-alpha03"
accompanist = "0.33.2-alpha" accompanist = "0.33.2-alpha"

View File

@ -1,6 +1,6 @@
[versions] [versions]
kotlin_version = "1.9.20" kotlin_version = "1.9.21"
serialization_version = "1.6.1" serialization_version = "1.6.2"
xml_serialization_version = "0.86.2" xml_serialization_version = "0.86.2"
[libraries] [libraries]

View File

@ -1,13 +1,13 @@
[versions] [versions]
aboutlib_version = "10.9.2" aboutlib_version = "10.9.2"
leakcanary = "2.12"
moko = "0.23.0" moko = "0.23.0"
okhttp_version = "5.0.0-alpha.11" okhttp_version = "5.0.0-alpha.11"
shizuku_version = "12.2.0"
sqlite = "2.4.0"
sqldelight = "2.0.0"
leakcanary = "2.12"
voyager = "1.0.0-rc10"
richtext = "0.17.0" richtext = "0.17.0"
shizuku_version = "12.2.0"
sqldelight = "2.0.0"
sqlite = "2.4.0"
voyager = "1.0.0"
[libraries] [libraries]
desugar = "com.android.tools:desugar_jdk_libs:2.0.4" desugar = "com.android.tools:desugar_jdk_libs:2.0.4"
@ -90,6 +90,7 @@ kotest-assertions = "io.kotest:kotest-assertions-core:5.8.0"
mockk = "io.mockk:mockk:1.13.8" mockk = "io.mockk:mockk:1.13.8"
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }
voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" } voyager-tab-navigator = { module = "cafe.adriel.voyager:voyager-tab-navigator", version.ref = "voyager" }
voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" } voyager-transitions = { module = "cafe.adriel.voyager:voyager-transitions", version.ref = "voyager" }
@ -104,6 +105,6 @@ sqlite = ["sqlite-framework", "sqlite-ktx", "sqlite-android"]
coil = ["coil-core", "coil-gif", "coil-compose"] coil = ["coil-core", "coil-gif", "coil-compose"]
shizuku = ["shizuku-api", "shizuku-provider"] shizuku = ["shizuku-api", "shizuku-provider"]
sqldelight = ["sqldelight-android-driver", "sqldelight-coroutines", "sqldelight-android-paging"] sqldelight = ["sqldelight-android-driver", "sqldelight-coroutines", "sqldelight-android-paging"]
voyager = ["voyager-navigator", "voyager-tab-navigator", "voyager-transitions"] voyager = ["voyager-navigator", "voyager-screenmodel", "voyager-tab-navigator", "voyager-transitions"]
richtext = ["richtext-commonmark", "richtext-m3"] richtext = ["richtext-commonmark", "richtext-m3"]
test = ["junit", "kotest-assertions", "mockk"] test = ["junit", "kotest-assertions", "mockk"]

5
i18n/README.md Normal file
View File

@ -0,0 +1,5 @@
# i18n
This module houses the string resources and translations.
Original English strings are manged in `src/commonMain/resources/MR/base/`. Translations are done externally via Weblate. See [our website](https://tachiyomi.org/docs/contribute#translation) for more details.

View File

@ -54,7 +54,6 @@
<string name="theme_dark">በርቷል</string> <string name="theme_dark">በርቷል</string>
<string name="theme_light">ጠፍቷል</string> <string name="theme_light">ጠፍቷል</string>
<string name="theme_system">ስርዓት ይከተሉ</string> <string name="theme_system">ስርዓት ይከተሉ</string>
<string name="pref_theme_mode">ጨለማ ሁነታ</string>
<string name="pref_category_theme">ገጽታ</string> <string name="pref_category_theme">ገጽታ</string>
<string name="pref_category_about">ስለ</string> <string name="pref_category_about">ስለ</string>
<string name="pref_category_advanced">የላቀ</string> <string name="pref_category_advanced">የላቀ</string>

View File

@ -309,7 +309,6 @@
<string name="theme_system">اتبع مظهر النظام</string> <string name="theme_system">اتبع مظهر النظام</string>
<string name="theme_dark">مفعّل</string> <string name="theme_dark">مفعّل</string>
<string name="theme_light">غير مفعّل</string> <string name="theme_light">غير مفعّل</string>
<string name="pref_theme_mode">الوضع الليلي</string>
<string name="battery_optimization_disabled">تم إلغاء وضع تحسين البطارية مُسبقاً</string> <string name="battery_optimization_disabled">تم إلغاء وضع تحسين البطارية مُسبقاً</string>
<string name="pref_disable_battery_optimization_summary">يساعد في عملية تحديث المكتبة والنسخ الإحتياطي في الخلفية</string> <string name="pref_disable_battery_optimization_summary">يساعد في عملية تحديث المكتبة والنسخ الإحتياطي في الخلفية</string>
<string name="pref_disable_battery_optimization">إطفاء وضع تحسين البطارية</string> <string name="pref_disable_battery_optimization">إطفاء وضع تحسين البطارية</string>

View File

@ -174,6 +174,19 @@
<!-- Shortcuts--> <!-- Shortcuts-->
<string name="app_not_available">App not available</string> <string name="app_not_available">App not available</string>
<!-- Onboarding -->
<string name="pref_onboarding_guide">Onboarding guide</string>
<string name="onboarding_heading">Welcome!</string>
<string name="onboarding_description">Let\'s set some things up first. You can always change these in the settings later too.</string>
<string name="onboarding_action_next">Next</string>
<string name="onboarding_action_finish">Get started</string>
<string name="onboarding_action_skip">Skip</string>
<string name="onboarding_storage_info">Select a folder where %1$s will store chapter downloads, backups, and more.\n\nA dedicated folder is recommended.\n\nSelected folder: %2$s</string>
<string name="onboarding_storage_action_select">Select a folder</string>
<string name="onboarding_storage_selection_required">A folder must be selected</string>
<string name="onboarding_guides_new_user">New to %s? We recommend checking out the getting started guide.</string>
<string name="onboarding_guides_returning_user">Already used %s before?</string>
<!-- Preferences --> <!-- Preferences -->
<!-- Subsections --> <!-- Subsections -->
<string name="pref_category_general">General</string> <string name="pref_category_general">General</string>
@ -198,11 +211,10 @@
<!-- General section --> <!-- General section -->
<string name="pref_category_theme">Theme</string> <string name="pref_category_theme">Theme</string>
<string name="pref_theme_mode">Dark mode</string>
<string name="theme_system">Follow system</string>
<string name="theme_light">Off</string>
<string name="theme_dark">On</string>
<string name="pref_app_theme">App theme</string> <string name="pref_app_theme">App theme</string>
<string name="theme_system">System</string>
<string name="theme_light">Light</string>
<string name="theme_dark">Dark</string>
<string name="theme_monet">Dynamic</string> <string name="theme_monet">Dynamic</string>
<string name="theme_greenapple">Green Apple</string> <string name="theme_greenapple">Green Apple</string>
<string name="theme_lavender">Lavender</string> <string name="theme_lavender">Lavender</string>
@ -436,6 +448,7 @@
<string name="pref_remove_after_read">After reading automatically delete</string> <string name="pref_remove_after_read">After reading automatically delete</string>
<string name="pref_remove_bookmarked_chapters">Allow deleting bookmarked chapters</string> <string name="pref_remove_bookmarked_chapters">Allow deleting bookmarked chapters</string>
<string name="pref_remove_exclude_categories">Excluded categories</string> <string name="pref_remove_exclude_categories">Excluded categories</string>
<string name="no_location_set">No storage location set</string>
<string name="invalid_location">Invalid location: %s</string> <string name="invalid_location">Invalid location: %s</string>
<string name="disabled">Disabled</string> <string name="disabled">Disabled</string>
<string name="last_read_chapter">Last read chapter</string> <string name="last_read_chapter">Last read chapter</string>

View File

@ -150,7 +150,6 @@
<string name="theme_dark">Уключаны</string> <string name="theme_dark">Уключаны</string>
<string name="theme_light">Выключаны</string> <string name="theme_light">Выключаны</string>
<string name="theme_system">У адпаведнасці з сістэмнай тэмай</string> <string name="theme_system">У адпаведнасці з сістэмнай тэмай</string>
<string name="pref_theme_mode">Цёмны рэжым</string>
<string name="pref_category_theme">Тэма</string> <string name="pref_category_theme">Тэма</string>
<string name="pref_category_about">Інфармацыя</string> <string name="pref_category_about">Інфармацыя</string>
<string name="pref_category_advanced">Дадаткова</string> <string name="pref_category_advanced">Дадаткова</string>

View File

@ -350,7 +350,6 @@
<string name="theme_system">Система на абонаментите</string> <string name="theme_system">Система на абонаментите</string>
<string name="theme_dark">Включено</string> <string name="theme_dark">Включено</string>
<string name="theme_light">Изключено</string> <string name="theme_light">Изключено</string>
<string name="pref_theme_mode">Тъмен режим</string>
<string name="action_move_to_top">Премести най-горе</string> <string name="action_move_to_top">Премести най-горе</string>
<string name="action_move_to_bottom">Премести най-долу</string> <string name="action_move_to_bottom">Премести най-долу</string>
<string name="action_oldest">Най-стари</string> <string name="action_oldest">Най-стари</string>

View File

@ -315,7 +315,6 @@
<string name="theme_dark">চালু করুন</string> <string name="theme_dark">চালু করুন</string>
<string name="theme_light">বন্ধ করুন</string> <string name="theme_light">বন্ধ করুন</string>
<string name="theme_system">সিস্টেমকে অনুসরণ করুন</string> <string name="theme_system">সিস্টেমকে অনুসরণ করুন</string>
<string name="pref_theme_mode">অন্ধকার মোড</string>
<string name="pref_category_theme">থিম</string> <string name="pref_category_theme">থিম</string>
<string name="action_move_to_bottom">নীচে সরান</string> <string name="action_move_to_bottom">নীচে সরান</string>
<string name="action_move_to_top">শীর্ষে সরান</string> <string name="action_move_to_top">শীর্ষে সরান</string>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">Darrer capítol</string> <string name="action_sort_latest_chapter">Darrer capítol</string>
<string name="action_view_chapters">Mostra els capítols</string> <string name="action_view_chapters">Mostra els capítols</string>
<string name="action_cancel_all">Cancel·la-ho tot</string> <string name="action_cancel_all">Cancel·la-ho tot</string>
<string name="pref_theme_mode">Mode fosc</string>
<string name="theme_light">Desactivat</string> <string name="theme_light">Desactivat</string>
<string name="theme_dark">Activat</string> <string name="theme_dark">Activat</string>
<string name="theme_system">Per defecte del sistema</string> <string name="theme_system">Per defecte del sistema</string>

View File

@ -115,7 +115,6 @@
<string name="pref_category_tracking">Pagsubay</string> <string name="pref_category_tracking">Pagsubay</string>
<string name="pref_category_advanced">Abante</string> <string name="pref_category_advanced">Abante</string>
<string name="pref_category_theme">Tema</string> <string name="pref_category_theme">Tema</string>
<string name="pref_theme_mode">Dark mode</string>
<string name="theme_dark">Sa</string> <string name="theme_dark">Sa</string>
<string name="pref_app_theme">Tema sa app</string> <string name="pref_app_theme">Tema sa app</string>
<string name="theme_monet">Dinamiko</string> <string name="theme_monet">Dinamiko</string>

View File

@ -392,7 +392,6 @@
<string name="lock_with_biometrics">Vyžadovat odemknutí</string> <string name="lock_with_biometrics">Vyžadovat odemknutí</string>
<string name="theme_dark">Zapnuto</string> <string name="theme_dark">Zapnuto</string>
<string name="theme_light">Vypnuto</string> <string name="theme_light">Vypnuto</string>
<string name="pref_theme_mode">Temný vzhled</string>
<string name="pref_category_theme">Vzhled</string> <string name="pref_category_theme">Vzhled</string>
<string name="action_move_to_top">Přesunout nahoru</string> <string name="action_move_to_top">Přesunout nahoru</string>
<string name="action_desc">Sestupně</string> <string name="action_desc">Sestupně</string>

View File

@ -155,7 +155,6 @@
<string name="action_open_log">Тӑвӑм-пулӑм кӗнекине уҫ</string> <string name="action_open_log">Тӑвӑм-пулӑм кӗнекине уҫ</string>
<string name="action_reset">Тасат</string> <string name="action_reset">Тасат</string>
<string name="action_sort">Уйӑр</string> <string name="action_sort">Уйӑр</string>
<string name="pref_theme_mode">Тӗксӗм темӑ</string>
<string name="theme_system">Системри пекех</string> <string name="theme_system">Системри пекех</string>
<string name="pref_category_reader">Вулӑш</string> <string name="pref_category_reader">Вулӑш</string>
<string name="pref_category_tracking">Йӗрлев</string> <string name="pref_category_tracking">Йӗрлев</string>

View File

@ -126,7 +126,6 @@
<string name="pref_category_advanced">Avanceret</string> <string name="pref_category_advanced">Avanceret</string>
<string name="pref_category_about">Om</string> <string name="pref_category_about">Om</string>
<string name="pref_category_theme">Tema</string> <string name="pref_category_theme">Tema</string>
<string name="pref_theme_mode">Mørk tilstand</string>
<string name="theme_system">Følg system</string> <string name="theme_system">Følg system</string>
<string name="theme_light">Fra</string> <string name="theme_light">Fra</string>
<string name="theme_dark">Til</string> <string name="theme_dark">Til</string>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">Neuestes Kapitel</string> <string name="action_sort_latest_chapter">Neuestes Kapitel</string>
<string name="action_view_chapters">Kapitel anzeigen</string> <string name="action_view_chapters">Kapitel anzeigen</string>
<string name="action_cancel_all">Alle abbrechen</string> <string name="action_cancel_all">Alle abbrechen</string>
<string name="pref_theme_mode">Dunkelmodus</string>
<string name="theme_light">Aus</string> <string name="theme_light">Aus</string>
<string name="theme_dark">An</string> <string name="theme_dark">An</string>
<string name="theme_system">Systemeinstellung</string> <string name="theme_system">Systemeinstellung</string>

View File

@ -350,7 +350,6 @@
<string name="theme_system">Ακολουθήστε το σύστημα</string> <string name="theme_system">Ακολουθήστε το σύστημα</string>
<string name="theme_dark">Ενεργοποιημένο</string> <string name="theme_dark">Ενεργοποιημένο</string>
<string name="theme_light">Απενεργοποιημένο</string> <string name="theme_light">Απενεργοποιημένο</string>
<string name="pref_theme_mode">Σκοτεινή λειτουργία</string>
<string name="action_move_to_bottom">Μετακίνηση στον πάτο</string> <string name="action_move_to_bottom">Μετακίνηση στον πάτο</string>
<string name="action_move_to_top">Μετακίνηση στην κορυφή</string> <string name="action_move_to_top">Μετακίνηση στην κορυφή</string>
<string name="action_cancel_all">Ακύρωση όλων</string> <string name="action_cancel_all">Ακύρωση όλων</string>

View File

@ -62,7 +62,6 @@
<string name="theme_dark">Ŝalti</string> <string name="theme_dark">Ŝalti</string>
<string name="theme_light">Malŝalti</string> <string name="theme_light">Malŝalti</string>
<string name="theme_system">Laŭ operaciumo</string> <string name="theme_system">Laŭ operaciumo</string>
<string name="pref_theme_mode">Malhela etoso</string>
<string name="pref_category_theme">Etoso</string> <string name="pref_category_theme">Etoso</string>
<string name="pref_category_about">Pri</string> <string name="pref_category_about">Pri</string>
<string name="pref_category_downloads">Elŝutoj</string> <string name="pref_category_downloads">Elŝutoj</string>

View File

@ -302,7 +302,6 @@
<string name="action_sort_latest_chapter">Por capítulo más reciente</string> <string name="action_sort_latest_chapter">Por capítulo más reciente</string>
<string name="action_view_chapters">Ver capítulos</string> <string name="action_view_chapters">Ver capítulos</string>
<string name="action_cancel_all">Cancelar todo</string> <string name="action_cancel_all">Cancelar todo</string>
<string name="pref_theme_mode">Modo oscuro</string>
<string name="theme_light">No</string> <string name="theme_light">No</string>
<string name="theme_dark"></string> <string name="theme_dark"></string>
<string name="theme_system">Según ajustes del sistema</string> <string name="theme_system">Según ajustes del sistema</string>

View File

@ -492,7 +492,6 @@
<string name="pref_category_downloads">Deskargak</string> <string name="pref_category_downloads">Deskargak</string>
<string name="pref_category_tracking">Jarraipena</string> <string name="pref_category_tracking">Jarraipena</string>
<string name="pref_category_advanced">Aurreratua</string> <string name="pref_category_advanced">Aurreratua</string>
<string name="pref_theme_mode">Modu iluna</string>
<string name="action_display_language_badge">Hizkuntza</string> <string name="action_display_language_badge">Hizkuntza</string>
<string name="theme_strawberrydaiquiri">Marrubi Daiquiri-a</string> <string name="theme_strawberrydaiquiri">Marrubi Daiquiri-a</string>
<string name="theme_tako">Tako</string> <string name="theme_tako">Tako</string>

View File

@ -261,7 +261,6 @@
<string name="theme_dark">روشن</string> <string name="theme_dark">روشن</string>
<string name="theme_light">خاموش</string> <string name="theme_light">خاموش</string>
<string name="theme_system">تم پیش‌فرض سیستم</string> <string name="theme_system">تم پیش‌فرض سیستم</string>
<string name="pref_theme_mode">تم تیره</string>
<string name="pref_category_about">درباره</string> <string name="pref_category_about">درباره</string>
<string name="pref_category_advanced">پیشرفته</string> <string name="pref_category_advanced">پیشرفته</string>
<string name="pref_category_tracking">ردیابی</string> <string name="pref_category_tracking">ردیابی</string>
@ -614,4 +613,19 @@
<string name="action_ok">باشه</string> <string name="action_ok">باشه</string>
<string name="action_sort_next_updated">به روز رسانی مورد انتظار بعدی</string> <string name="action_sort_next_updated">به روز رسانی مورد انتظار بعدی</string>
<string name="download_queue_size_warning">هشدار: حجم زیاد بارگیری ممکن است باعث اهسته تر شدن سرعت ویا مسدود کردن Tachiyomi از منبع شود. برای اطلاعات بیشتر لمس کنید.</string> <string name="download_queue_size_warning">هشدار: حجم زیاد بارگیری ممکن است باعث اهسته تر شدن سرعت ویا مسدود کردن Tachiyomi از منبع شود. برای اطلاعات بیشتر لمس کنید.</string>
<string name="skipped_reason_not_always_update">به دلیل این که این مجموعه نیازی به به روز رسانی نداشت رد شد</string>
<string name="skipped_reason_not_caught_up">به دلیل وجود چپتر های خوانده نشده رد شد</string>
<string name="notification_update_skipped">%1$d بزور رسانی رد شد</string>
<string name="skipped_reason_not_started">به دلیل این که هیچ چپتری خوانده نشده بود رد شد</string>
<string name="notification_update_error">%1$d بروز رسانی ناموفق</string>
<string name="pref_relative_format_summary">\"%1$s\" به جای \"%2$s\"</string>
<string name="pref_library_columns_per_row">%d در هر ردیف</string>
<string name="pref_page_rotate">صفحات عریض را بچرخان تا جا شوند</string>
<string name="popular">محبوب</string>
<string name="pref_chapter_swipe_end">حرکت کشیدن به راست</string>
<string name="pref_flash_page">در هنگام عوض شدن صفحه فلاش سفید بزن</string>
<string name="pref_chapter_swipe_start">حرکت کشیدن به چپ</string>
<string name="updates_last_update_info">آخرین به روز رسانی کتابخانه: %s</string>
<string name="pref_update_only_in_release_period">خارج از دوره انتشار موزد انتظار</string>
<string name="pref_double_tap_zoom">برای بزرگ نمایی دوبار ضربه بزنید</string>
</resources> </resources>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">Viimeisin luku</string> <string name="action_sort_latest_chapter">Viimeisin luku</string>
<string name="action_view_chapters">Näytä luvut</string> <string name="action_view_chapters">Näytä luvut</string>
<string name="action_cancel_all">Peruuta kaikki</string> <string name="action_cancel_all">Peruuta kaikki</string>
<string name="pref_theme_mode">Pimeä tila</string>
<string name="theme_light">Pois päältä</string> <string name="theme_light">Pois päältä</string>
<string name="theme_dark">Päällä</string> <string name="theme_dark">Päällä</string>
<string name="theme_system">Seuraa järjestelmää</string> <string name="theme_system">Seuraa järjestelmää</string>

View File

@ -102,7 +102,6 @@
<string name="theme_dark">Nakabukas</string> <string name="theme_dark">Nakabukas</string>
<string name="theme_light">Nakasara</string> <string name="theme_light">Nakasara</string>
<string name="theme_system">Sundan ang sistema</string> <string name="theme_system">Sundan ang sistema</string>
<string name="pref_theme_mode">Madilim na tema</string>
<string name="pref_category_about">Patungkol</string> <string name="pref_category_about">Patungkol</string>
<string name="pref_category_advanced">Karagdagan</string> <string name="pref_category_advanced">Karagdagan</string>
<string name="pref_category_tracking">Pagta-track</string> <string name="pref_category_tracking">Pagta-track</string>
@ -272,7 +271,7 @@
<string name="notification_chapters_single_and_more">Kabanata %1$s at karagdagang %2$d pa</string> <string name="notification_chapters_single_and_more">Kabanata %1$s at karagdagang %2$d pa</string>
<string name="notification_chapters_single">Kabanata %1$s</string> <string name="notification_chapters_single">Kabanata %1$s</string>
<string name="notification_new_chapters">May mga bagong kabanata</string> <string name="notification_new_chapters">May mga bagong kabanata</string>
<string name="download_insufficient_space">Di ma-download ang mga kabanata dahil sa mababang espasyo</string> <string name="download_insufficient_space">Di ma-download ang mga kabanata dahil sa mababang espasyo sa storage</string>
<string name="download_queue_error">Di ma-download ang mga kabanata. Subukan mo uli ito sa Dina-download</string> <string name="download_queue_error">Di ma-download ang mga kabanata. Subukan mo uli ito sa Dina-download</string>
<string name="copy">Kopyahin</string> <string name="copy">Kopyahin</string>
<string name="migrate">Ilipat</string> <string name="migrate">Ilipat</string>
@ -644,7 +643,7 @@
<string name="pref_downloads_summary">Kusang pag-download, i-download agad</string> <string name="pref_downloads_summary">Kusang pag-download, i-download agad</string>
<string name="pref_tracking_summary">Isahang pagsabay sa progress, pinahusay na pagsabay</string> <string name="pref_tracking_summary">Isahang pagsabay sa progress, pinahusay na pagsabay</string>
<string name="pref_appearance_summary">Tema, ayos ng petsa &amp; oras</string> <string name="pref_appearance_summary">Tema, ayos ng petsa &amp; oras</string>
<string name="pref_backup_summary">Mano-mano at kusang pag-backup</string> <string name="pref_backup_summary">Mano-mano at awtomatikong pag-backup, espasyo sa storage</string>
<string name="pref_security_summary">Pag-lock aa app, bantayan ang screen</string> <string name="pref_security_summary">Pag-lock aa app, bantayan ang screen</string>
<string name="pref_advanced_summary">Itambak ang mga crash log, pag-o-optimisa sa baterya</string> <string name="pref_advanced_summary">Itambak ang mga crash log, pag-o-optimisa sa baterya</string>
<string name="pref_library_summary">Mga kategorya, panlahatang update, pag-swipe ng kabanata</string> <string name="pref_library_summary">Mga kategorya, panlahatang update, pag-swipe ng kabanata</string>
@ -764,7 +763,7 @@
<string name="exclude_scanlators">Ibukod ang mga scanlator</string> <string name="exclude_scanlators">Ibukod ang mga scanlator</string>
<string name="action_create">Lumikha</string> <string name="action_create">Lumikha</string>
<string name="pref_storage_location">Lokasyon ng storage</string> <string name="pref_storage_location">Lokasyon ng storage</string>
<string name="pref_storage_location_info">Ginagamit para sa automatikong pa-backup, pag-download ng mga kabanata, at lokal na source.</string> <string name="pref_storage_location_info">Ginagamit para sa automatikong pag-backup, pag-download ng mga kabanata, at lokal na source.</string>
<string name="action_menu_overflow_description">Ibang opsiyon</string> <string name="action_menu_overflow_description">Ibang opsiyon</string>
<string name="selected">Napili</string> <string name="selected">Napili</string>
<string name="not_selected">Di napili</string> <string name="not_selected">Di napili</string>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">Dernier chapitre</string> <string name="action_sort_latest_chapter">Dernier chapitre</string>
<string name="action_view_chapters">Voir les chapitres</string> <string name="action_view_chapters">Voir les chapitres</string>
<string name="action_cancel_all">Tout annuler</string> <string name="action_cancel_all">Tout annuler</string>
<string name="pref_theme_mode">Mode sombre</string>
<string name="theme_light">Désactivé</string> <string name="theme_light">Désactivé</string>
<string name="theme_dark">Activé</string> <string name="theme_dark">Activé</string>
<string name="theme_system">Par défaut du système</string> <string name="theme_system">Par défaut du système</string>

View File

@ -138,7 +138,6 @@
<string name="theme_dark">Activado</string> <string name="theme_dark">Activado</string>
<string name="theme_light">Desactivado</string> <string name="theme_light">Desactivado</string>
<string name="theme_system">Utilizar o do sistema</string> <string name="theme_system">Utilizar o do sistema</string>
<string name="pref_theme_mode">Modo escuro</string>
<string name="pref_category_theme">Tema</string> <string name="pref_category_theme">Tema</string>
<string name="pref_category_about">Acerca de</string> <string name="pref_category_about">Acerca de</string>
<string name="pref_category_advanced">Avanzado</string> <string name="pref_category_advanced">Avanzado</string>

View File

@ -141,7 +141,6 @@
<string name="pref_date_format">תבנית תאריך</string> <string name="pref_date_format">תבנית תאריך</string>
<string name="theme_dark">פעיל</string> <string name="theme_dark">פעיל</string>
<string name="theme_light">כבוי</string> <string name="theme_light">כבוי</string>
<string name="pref_theme_mode">מצב חשוך</string>
<string name="pref_category_about">אודות</string> <string name="pref_category_about">אודות</string>
<string name="pref_category_advanced">מתקדם</string> <string name="pref_category_advanced">מתקדם</string>
<string name="pref_category_downloads">הורדות</string> <string name="pref_category_downloads">הורדות</string>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">नवीनतम अध्याय</string> <string name="action_sort_latest_chapter">नवीनतम अध्याय</string>
<string name="action_view_chapters">अध्याय देखें</string> <string name="action_view_chapters">अध्याय देखें</string>
<string name="action_cancel_all">सब रद्द करो</string> <string name="action_cancel_all">सब रद्द करो</string>
<string name="pref_theme_mode">डार्क मोड</string>
<string name="theme_light">बंद</string> <string name="theme_light">बंद</string>
<string name="theme_dark">चालू करे</string> <string name="theme_dark">चालू करे</string>
<string name="theme_system">सिस्टम का पालन करें</string> <string name="theme_system">सिस्टम का पालन करें</string>

View File

@ -50,7 +50,6 @@
<string name="theme_system">Slijedi sustav</string> <string name="theme_system">Slijedi sustav</string>
<string name="theme_dark">Uključeno</string> <string name="theme_dark">Uključeno</string>
<string name="theme_light">Isključeno</string> <string name="theme_light">Isključeno</string>
<string name="pref_theme_mode">Tamna tema</string>
<string name="pref_category_about">Informacije</string> <string name="pref_category_about">Informacije</string>
<string name="pref_category_advanced">Napredno</string> <string name="pref_category_advanced">Napredno</string>
<string name="pref_category_tracking">Praćenje</string> <string name="pref_category_tracking">Praćenje</string>

View File

@ -176,7 +176,6 @@
<string name="pref_category_security">Biztonság</string> <string name="pref_category_security">Biztonság</string>
<string name="pref_manage_notifications">Értesítések kezelése</string> <string name="pref_manage_notifications">Értesítések kezelése</string>
<string name="theme_system">Rendszerbeállítás követése</string> <string name="theme_system">Rendszerbeállítás követése</string>
<string name="pref_theme_mode">Sötét mód</string>
<string name="pref_category_theme">Téma</string> <string name="pref_category_theme">Téma</string>
<string name="action_move_to_bottom">Ugrás legalulra</string> <string name="action_move_to_bottom">Ugrás legalulra</string>
<string name="action_move_to_top">Ugrás legfelülre</string> <string name="action_move_to_top">Ugrás legfelülre</string>

View File

@ -293,7 +293,6 @@
<string name="action_sort_latest_chapter">Bab terbaru</string> <string name="action_sort_latest_chapter">Bab terbaru</string>
<string name="action_view_chapters">Lihat bab</string> <string name="action_view_chapters">Lihat bab</string>
<string name="action_cancel_all">Batalkan semua</string> <string name="action_cancel_all">Batalkan semua</string>
<string name="pref_theme_mode">Mode gelap</string>
<string name="theme_light">Mati</string> <string name="theme_light">Mati</string>
<string name="theme_dark">Nyala</string> <string name="theme_dark">Nyala</string>
<string name="theme_system">Ikuti sistem</string> <string name="theme_system">Ikuti sistem</string>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">Ultimo capitolo</string> <string name="action_sort_latest_chapter">Ultimo capitolo</string>
<string name="action_view_chapters">Visualizza capitoli</string> <string name="action_view_chapters">Visualizza capitoli</string>
<string name="action_cancel_all">Annulla tutto</string> <string name="action_cancel_all">Annulla tutto</string>
<string name="pref_theme_mode">Tema scuro</string>
<string name="theme_light">Disattivo</string> <string name="theme_light">Disattivo</string>
<string name="theme_dark">Attivo</string> <string name="theme_dark">Attivo</string>
<string name="theme_system">Usa il tema di sistema</string> <string name="theme_system">Usa il tema di sistema</string>

View File

@ -296,7 +296,6 @@
<string name="action_sort_latest_chapter">最新章の更新順</string> <string name="action_sort_latest_chapter">最新章の更新順</string>
<string name="action_view_chapters">章を見る</string> <string name="action_view_chapters">章を見る</string>
<string name="action_cancel_all">すべてキャンセル</string> <string name="action_cancel_all">すべてキャンセル</string>
<string name="pref_theme_mode">ダークモード</string>
<string name="theme_light">オフ</string> <string name="theme_light">オフ</string>
<string name="theme_dark">オン</string> <string name="theme_dark">オン</string>
<string name="theme_system">システムに従う</string> <string name="theme_system">システムに従う</string>
@ -726,8 +725,8 @@
<string name="track_delete_text">ローカルの追跡が削除されます。</string> <string name="track_delete_text">ローカルの追跡が削除されます。</string>
<string name="track_delete_remote_text">%s からも削除</string> <string name="track_delete_remote_text">%s からも削除</string>
<string name="delete_downloaded">ダウンロードを削除</string> <string name="delete_downloaded">ダウンロードを削除</string>
<string name="action_filter_interval_late">10+チェック後半</string> <string name="action_filter_interval_late">レイト10+チェック</string>
<string name="action_filter_interval_dropped">とした? 20歳以上後半と2ヶ月</string> <string name="action_filter_interval_dropped">選? 20歳後半と2ヶ月</string>
<string name="action_filter_interval_passed">チェック期間を過ぎました</string> <string name="action_filter_interval_passed">チェック期間を過ぎました</string>
<string name="action_ok">OK</string> <string name="action_ok">OK</string>
<string name="syncing_library">ライブラリを同期しています</string> <string name="syncing_library">ライブラリを同期しています</string>

View File

@ -42,7 +42,6 @@
<string name="pref_date_format">Format tanggal</string> <string name="pref_date_format">Format tanggal</string>
<string name="theme_dark">Murup</string> <string name="theme_dark">Murup</string>
<string name="theme_light">Mati</string> <string name="theme_light">Mati</string>
<string name="pref_theme_mode">Mode Peteng</string>
<string name="pref_category_theme">Tema</string> <string name="pref_category_theme">Tema</string>
<string name="pref_category_downloads">Donlot</string> <string name="pref_category_downloads">Donlot</string>
<string name="action_webview_back">Bali</string> <string name="action_webview_back">Bali</string>

View File

@ -79,7 +79,6 @@
<string name="pref_category_tracking">თვალყურის დევნება</string> <string name="pref_category_tracking">თვალყურის დევნება</string>
<string name="pref_category_advanced">დამატებით</string> <string name="pref_category_advanced">დამატებით</string>
<string name="pref_category_about">ინფორმაცია</string> <string name="pref_category_about">ინფორმაცია</string>
<string name="pref_theme_mode">ბნელი რეჟიმი</string>
<string name="theme_system">სისტემური</string> <string name="theme_system">სისტემური</string>
<string name="theme_light">მსუბუქი</string> <string name="theme_light">მსუბუქი</string>
<string name="theme_dark">ჩართული</string> <string name="theme_dark">ჩართული</string>

View File

@ -57,7 +57,6 @@
<string name="pref_category_downloads">Жүктеулер</string> <string name="pref_category_downloads">Жүктеулер</string>
<string name="pref_category_advanced">Толығырақ</string> <string name="pref_category_advanced">Толығырақ</string>
<string name="pref_category_theme">Кейіп</string> <string name="pref_category_theme">Кейіп</string>
<string name="pref_theme_mode">Қараңғы режим</string>
<string name="theme_system">Жүйе бойынша</string> <string name="theme_system">Жүйе бойынша</string>
<string name="theme_light">Өшірулі</string> <string name="theme_light">Өшірулі</string>
<string name="theme_dark">Қосулы</string> <string name="theme_dark">Қосулы</string>

View File

@ -120,7 +120,6 @@
<string name="pref_category_advanced">ការកំណត់ពិសេស</string> <string name="pref_category_advanced">ការកំណត់ពិសេស</string>
<string name="pref_category_about">អំពីរ</string> <string name="pref_category_about">អំពីរ</string>
<string name="pref_category_theme">ពណ៌</string> <string name="pref_category_theme">ពណ៌</string>
<string name="pref_theme_mode">ខ្មៅ</string>
<string name="theme_system">តាមទូរសព្ទ</string> <string name="theme_system">តាមទូរសព្ទ</string>
<string name="theme_light">បិទ</string> <string name="theme_light">បិទ</string>
<string name="theme_dark">បើក</string> <string name="theme_dark">បើក</string>

View File

@ -17,7 +17,6 @@
<string name="theme_system">ಸಿಸ್ಟಮ್ ಅನುಕರಿಸಿ</string> <string name="theme_system">ಸಿಸ್ಟಮ್ ಅನುಕರಿಸಿ</string>
<string name="theme_dark">ಆನ್</string> <string name="theme_dark">ಆನ್</string>
<string name="theme_light">ಆಫ</string> <string name="theme_light">ಆಫ</string>
<string name="pref_theme_mode">ಡಾರ್ಕ್ ಮೋಡ್</string>
<string name="pref_category_about">ಅಪ್ಲಿಕೇಶನ್ ಬಗ್ಗೆ</string> <string name="pref_category_about">ಅಪ್ಲಿಕೇಶನ್ ಬಗ್ಗೆ</string>
<string name="pref_category_advanced">ಸುಧಾರಿತ</string> <string name="pref_category_advanced">ಸುಧಾರಿತ</string>
<string name="pref_category_tracking">ಟ್ರ್ಯಾಕಿಂಗ್</string> <string name="pref_category_tracking">ಟ್ರ್ಯಾಕಿಂಗ್</string>

View File

@ -342,7 +342,6 @@
<string name="theme_dark">켜기</string> <string name="theme_dark">켜기</string>
<string name="theme_light">끄기</string> <string name="theme_light">끄기</string>
<string name="theme_system">시스템 설정 사용</string> <string name="theme_system">시스템 설정 사용</string>
<string name="pref_theme_mode">다크 모드</string>
<string name="action_disable_all">모두 비활성화</string> <string name="action_disable_all">모두 비활성화</string>
<string name="spen_next_page">다음 페이지</string> <string name="spen_next_page">다음 페이지</string>
<string name="confirm_lock_change">변경 확인을 위해 인증이 필요합니다</string> <string name="confirm_lock_change">변경 확인을 위해 인증이 필요합니다</string>

View File

@ -73,7 +73,6 @@
<string name="theme_dark">Įjungta</string> <string name="theme_dark">Įjungta</string>
<string name="theme_light">Išjungta</string> <string name="theme_light">Išjungta</string>
<string name="theme_system">Pagal sistemą</string> <string name="theme_system">Pagal sistemą</string>
<string name="pref_theme_mode">Tamsi</string>
<string name="pref_category_theme">Tema</string> <string name="pref_category_theme">Tema</string>
<string name="pref_category_about">Apie</string> <string name="pref_category_about">Apie</string>
<string name="pref_category_advanced">Papildomi</string> <string name="pref_category_advanced">Papildomi</string>

View File

@ -139,7 +139,6 @@
<string name="theme_dark">Ieslēgts</string> <string name="theme_dark">Ieslēgts</string>
<string name="theme_light">Izslēgts</string> <string name="theme_light">Izslēgts</string>
<string name="theme_system">Sekot sistēmu</string> <string name="theme_system">Sekot sistēmu</string>
<string name="pref_theme_mode">Tumšais režīms</string>
<string name="pref_category_theme">Motīvs</string> <string name="pref_category_theme">Motīvs</string>
<string name="pref_category_library">Bibliotēka</string> <string name="pref_category_library">Bibliotēka</string>
<string name="action_webview_refresh">Atjaunot</string> <string name="action_webview_refresh">Atjaunot</string>
@ -744,4 +743,29 @@
<string name="action_sort_category">Kārtot kategorijas</string> <string name="action_sort_category">Kārtot kategorijas</string>
<string name="action_sort_tracker_score">Izsekošanas rezultāts</string> <string name="action_sort_tracker_score">Izsekošanas rezultāts</string>
<string name="label_data_storage">Dati un uzglabāšana</string> <string name="label_data_storage">Dati un uzglabāšana</string>
<string name="pref_storage_location">Krātuves atrašanās vieta</string>
<string name="action_create">Izveidot</string>
<string name="relative_time_span_never">Nekad</string>
<string name="pref_flash_page_summ">Samazina spoku rašanos uz e-ink displejiem</string>
<string name="pref_storage_location_info">Izmanto automātiskajām dublējumkopijām, nodaļu lejupielādei un vietējam avotam.</string>
<string name="action_apply">Pieteikties</string>
<string name="action_revert_to_default">Atgriezt noklusējuma iestatījumus</string>
<string name="action_menu_overflow_description">Vairāk iespēju</string>
<string name="last_auto_backup_info">Pēdējā automātiskā dublēšana: %s</string>
<string name="selected">Atlasīts</string>
<string name="no_scanlators_found">Nav atrasts neviens scanlators</string>
<string name="not_selected">Nav atlasīts</string>
<string name="action_move_to_bottom_all_for_series">Pārvietot sēriju uz apakšu</string>
<string name="scanlator">Skanlators</string>
<string name="pref_flash_page">Zibsnīt baltu, kad maina lapu</string>
<string name="pref_storage_usage">Krātuves izmantošana</string>
<string name="notification_updating_progress">Bibliotēkas atjaunināšana... (%s)</string>
<string name="action_bar_up_description">Virzīties uz augšu</string>
<string name="sort_category_confirmation">Vai vēlaties kategorijas sakārtot pēc alfabēta?</string>
<string name="file_null_uri_error">Nav atlasīts neviens fails</string>
<string name="source_settings">Avota iestatījumi</string>
<string name="app_settings">Lietotnes iestatījumi</string>
<string name="pref_relative_format">Relatīviās laika stampas</string>
<string name="pref_relative_format_summary">\"%1$s\", nevis \"%2$s\"</string>
<string name="exclude_scanlators">Izslēgt skanlatorus</string>
</resources> </resources>

View File

@ -97,7 +97,6 @@
<string name="theme_system">तंत्राचे अनुसरण करा</string> <string name="theme_system">तंत्राचे अनुसरण करा</string>
<string name="theme_dark">डार्क</string> <string name="theme_dark">डार्क</string>
<string name="theme_light">लाइट</string> <string name="theme_light">लाइट</string>
<string name="pref_theme_mode">गडद मोड</string>
<string name="pref_category_about">ॲप बद्दल</string> <string name="pref_category_about">ॲप बद्दल</string>
<string name="pref_category_advanced">प्रगत</string> <string name="pref_category_advanced">प्रगत</string>
<string name="pref_category_tracking">ट्रॅकिंग</string> <string name="pref_category_tracking">ट्रॅकिंग</string>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">Bab terkini</string> <string name="action_sort_latest_chapter">Bab terkini</string>
<string name="action_view_chapters">Buka bab</string> <string name="action_view_chapters">Buka bab</string>
<string name="action_cancel_all">Batalkan semua</string> <string name="action_cancel_all">Batalkan semua</string>
<string name="pref_theme_mode">Tema gelap</string>
<string name="theme_light">Mati</string> <string name="theme_light">Mati</string>
<string name="theme_dark">Hidup</string> <string name="theme_dark">Hidup</string>
<string name="theme_system">Ikut sistem</string> <string name="theme_system">Ikut sistem</string>

View File

@ -300,7 +300,6 @@
<string name="label_more">Mer</string> <string name="label_more">Mer</string>
<string name="action_view_chapters">Vis kapitler</string> <string name="action_view_chapters">Vis kapitler</string>
<string name="action_cancel_all">Avbryt alle</string> <string name="action_cancel_all">Avbryt alle</string>
<string name="pref_theme_mode">Mørk</string>
<string name="theme_light">Av</string> <string name="theme_light">Av</string>
<string name="theme_dark"></string> <string name="theme_dark"></string>
<string name="theme_system">System</string> <string name="theme_system">System</string>

View File

@ -2,7 +2,6 @@
<resources> <resources>
<string name="theme_dark">अन</string> <string name="theme_dark">अन</string>
<string name="theme_light">अफ</string> <string name="theme_light">अफ</string>
<string name="pref_theme_mode">अँध्यारो मोड</string>
<string name="pref_category_theme">थीम</string> <string name="pref_category_theme">थीम</string>
<string name="pref_category_about">बारेमा</string> <string name="pref_category_about">बारेमा</string>
<string name="pref_category_advanced">उन्नत सेटिङहरू</string> <string name="pref_category_advanced">उन्नत सेटिङहरू</string>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">Laatste hoofdstuk</string> <string name="action_sort_latest_chapter">Laatste hoofdstuk</string>
<string name="action_view_chapters">Hoofdstukken bekijken</string> <string name="action_view_chapters">Hoofdstukken bekijken</string>
<string name="action_cancel_all">Alles annuleren</string> <string name="action_cancel_all">Alles annuleren</string>
<string name="pref_theme_mode">Donkere modus</string>
<string name="theme_light">Uit</string> <string name="theme_light">Uit</string>
<string name="theme_dark">Aan</string> <string name="theme_dark">Aan</string>
<string name="theme_system">Volg systeeminstelling</string> <string name="theme_system">Volg systeeminstelling</string>

View File

@ -316,7 +316,6 @@
<string name="theme_system">Systemowy</string> <string name="theme_system">Systemowy</string>
<string name="theme_dark">Włącz</string> <string name="theme_dark">Włącz</string>
<string name="theme_light">Wyłącz</string> <string name="theme_light">Wyłącz</string>
<string name="pref_theme_mode">Ciemny motyw</string>
<string name="action_move_to_bottom">Przenieś na dół</string> <string name="action_move_to_bottom">Przenieś na dół</string>
<string name="action_move_to_top">Przenieś na górę</string> <string name="action_move_to_top">Przenieś na górę</string>
<string name="action_oldest">Najstarsze</string> <string name="action_oldest">Najstarsze</string>

View File

@ -301,10 +301,9 @@
<string name="action_sort_latest_chapter">Último capítulo</string> <string name="action_sort_latest_chapter">Último capítulo</string>
<string name="action_view_chapters">Visualizar os capítulos</string> <string name="action_view_chapters">Visualizar os capítulos</string>
<string name="action_cancel_all">Cancelar todos</string> <string name="action_cancel_all">Cancelar todos</string>
<string name="pref_theme_mode">Modo noturno</string> <string name="theme_light">Claro</string>
<string name="theme_light">Desligado</string> <string name="theme_dark">Escuro</string>
<string name="theme_dark">Ligado</string> <string name="theme_system">Sistema</string>
<string name="theme_system">Seguir o sistema</string>
<string name="pref_manage_notifications">Gerenciar notificações</string> <string name="pref_manage_notifications">Gerenciar notificações</string>
<string name="pref_category_security">Segurança e privacidade</string> <string name="pref_category_security">Segurança e privacidade</string>
<string name="lock_with_biometrics">Exigir desbloqueio</string> <string name="lock_with_biometrics">Exigir desbloqueio</string>
@ -769,4 +768,19 @@
<string name="pref_storage_location_info">Usado para backups automáticos, downloads de capítulos e na fonte local.</string> <string name="pref_storage_location_info">Usado para backups automáticos, downloads de capítulos e na fonte local.</string>
<string name="action_menu_overflow_description">Mais opções</string> <string name="action_menu_overflow_description">Mais opções</string>
<string name="action_bar_up_description">Navegar para cima</string> <string name="action_bar_up_description">Navegar para cima</string>
<string name="onboarding_storage_action_select">Selecionar uma pasta</string>
<string name="pref_onboarding_guide">Guia de introdução</string>
<string name="onboarding_guides_new_user">Novo no %s? Recomendamos dar uma olhada no guia de introdução.</string>
<string name="onboarding_action_finish">Começar</string>
<string name="onboarding_heading">Bem-vindo(a)!</string>
<string name="onboarding_guides_returning_user">Já utilizou o %s antes?</string>
<string name="onboarding_action_skip">Pular</string>
<string name="onboarding_action_next">Próximo</string>
<string name="onboarding_description">Vamos definir algumas coisas primeiro. Você sempre pode fazer alterações nas configurações depois também.</string>
<string name="no_location_set">Local de armazenamento não definido</string>
<string name="onboarding_storage_info">Escolha uma pasta onde o %1$s irá armazenar os downloads de capítulos, backups e mais.
\n
\nUma pasta dedicada é recomendada.
\n
\nPasta selecionada: %2$s</string>
</resources> </resources>

View File

@ -301,7 +301,6 @@
<string name="action_sort_latest_chapter">Último capítulo</string> <string name="action_sort_latest_chapter">Último capítulo</string>
<string name="action_view_chapters">Ver capítulos</string> <string name="action_view_chapters">Ver capítulos</string>
<string name="action_cancel_all">Cancelar tudo</string> <string name="action_cancel_all">Cancelar tudo</string>
<string name="pref_theme_mode">Modo escuro</string>
<string name="theme_light">Desligado</string> <string name="theme_light">Desligado</string>
<string name="theme_dark">Ligado</string> <string name="theme_dark">Ligado</string>
<string name="theme_system">Seguir o do sistema</string> <string name="theme_system">Seguir o do sistema</string>

View File

@ -65,4 +65,19 @@
<item quantity="few">Următoarele %d capitole necitite</item> <item quantity="few">Următoarele %d capitole necitite</item>
<item quantity="other">Următoarele %d capitole necitite</item> <item quantity="other">Următoarele %d capitole necitite</item>
</plurals> </plurals>
<plurals name="download_amount">
<item quantity="one">Următorul capitol</item>
<item quantity="few">Următoarele %d capitole</item>
<item quantity="other">Următoarele %d capitole</item>
</plurals>
<plurals name="missing_chapters">
<item quantity="one">Lipsește %1$s capitol</item>
<item quantity="few">Lipsesc %1$s capitole</item>
<item quantity="other">Lipsesc %1$s capitole</item>
</plurals>
<plurals name="day">
<item quantity="one">O zi</item>
<item quantity="few">%d zile</item>
<item quantity="other">%d zile</item>
</plurals>
</resources> </resources>

View File

@ -173,7 +173,7 @@
<string name="cookies_cleared">Cookies curățate</string> <string name="cookies_cleared">Cookies curățate</string>
<string name="pref_clear_database">Curățați baza de date</string> <string name="pref_clear_database">Curățați baza de date</string>
<string name="pref_clear_database_summary">Ștergeți istoricul pentru intrările care nu sunt salvate în bibliotecă</string> <string name="pref_clear_database_summary">Ștergeți istoricul pentru intrările care nu sunt salvate în bibliotecă</string>
<string name="clear_database_confirmation">Ești sigur\? Capitolele citite și progresul intrărilor din afara bibliotecii vor fi pierdute</string> <string name="clear_database_confirmation">Ești sigur? Capitolele citite și progresul înregistrărilor din afara bibliotecii vor fi pierdute</string>
<string name="clear_database_completed">Înregistrări șterse</string> <string name="clear_database_completed">Înregistrări șterse</string>
<string name="version">Versiune</string> <string name="version">Versiune</string>
<string name="pref_enable_acra">Trimite rapoarte pt. eșuări</string> <string name="pref_enable_acra">Trimite rapoarte pt. eșuări</string>
@ -300,7 +300,6 @@
<string name="label_more">Mai multe</string> <string name="label_more">Mai multe</string>
<string name="action_view_chapters">Vezi capitolele</string> <string name="action_view_chapters">Vezi capitolele</string>
<string name="action_cancel_all">Anulează tot</string> <string name="action_cancel_all">Anulează tot</string>
<string name="pref_theme_mode">Mod întunecat</string>
<string name="theme_light">Oprită</string> <string name="theme_light">Oprită</string>
<string name="theme_dark">Pornită</string> <string name="theme_dark">Pornită</string>
<string name="theme_system">Tema sistemului</string> <string name="theme_system">Tema sistemului</string>
@ -331,7 +330,7 @@
<string name="action_menu">Meniu</string> <string name="action_menu">Meniu</string>
<string name="action_newest">Cele mai recente</string> <string name="action_newest">Cele mai recente</string>
<string name="action_oldest">Cele mai vechi</string> <string name="action_oldest">Cele mai vechi</string>
<string name="action_move_to_top">Mută pe primă poziție</string> <string name="action_move_to_top">Mută pe prima poziție</string>
<string name="action_move_to_bottom">Mută pe ultima poziție</string> <string name="action_move_to_bottom">Mută pe ultima poziție</string>
<string name="channel_ext_updates">Actualizări de extensie</string> <string name="channel_ext_updates">Actualizări de extensie</string>
<string name="updating_library">Se actualizează biblioteca</string> <string name="updating_library">Se actualizează biblioteca</string>
@ -527,7 +526,7 @@
<string name="pref_hide_threshold">Sensibilitatea pentru ascunderea meniului la defilare</string> <string name="pref_hide_threshold">Sensibilitatea pentru ascunderea meniului la defilare</string>
<string name="action_show_manga">Afișează intrarea</string> <string name="action_show_manga">Afișează intrarea</string>
<string name="ext_installer_shizuku_unavailable_dialog">Instalați și porniți Shizuku pentru a utiliza Shizuku ca instalator de extensii.</string> <string name="ext_installer_shizuku_unavailable_dialog">Instalați și porniți Shizuku pentru a utiliza Shizuku ca instalator de extensii.</string>
<string name="clear_database_source_item_count">%1$d intrări non-bibliotecare în baza de date</string> <string name="clear_database_source_item_count">%1$d înregistrări în baza de datecare nu aparțin bibliotecii</string>
<string name="download_notifier_split_page_not_found">Pagina %d nu a fost găsită în timpul divizării</string> <string name="download_notifier_split_page_not_found">Pagina %d nu a fost găsită în timpul divizării</string>
<string name="delete_category_confirmation">Doriți să ștergeți categoria \"%s\"\?</string> <string name="delete_category_confirmation">Doriți să ștergeți categoria \"%s\"\?</string>
<string name="internal_error">InternalError: Verificați jurnalele de accident pentru informații suplimentare</string> <string name="internal_error">InternalError: Verificați jurnalele de accident pentru informații suplimentare</string>
@ -544,7 +543,7 @@
<string name="wish_list">Lista de dorințe</string> <string name="wish_list">Lista de dorințe</string>
<string name="complete_list">Lista completă</string> <string name="complete_list">Lista completă</string>
<string name="action_display_cover_only_grid">Acoperire tip grilaj</string> <string name="action_display_cover_only_grid">Acoperire tip grilaj</string>
<string name="action_move_to_top_all_for_series">Mutați seria în partea de sus</string> <string name="action_move_to_top_all_for_series">Mutați seria pe prima poziție</string>
<string name="pref_library_update_manga_restriction">Omiteți actualizarea intrărilor</string> <string name="pref_library_update_manga_restriction">Omiteți actualizarea intrărilor</string>
<string name="pref_update_only_started">Care nu au fost începute</string> <string name="pref_update_only_started">Care nu au fost începute</string>
<string name="ext_installer_shizuku_stopped">Shizuku nu rulează</string> <string name="ext_installer_shizuku_stopped">Shizuku nu rulează</string>
@ -583,7 +582,7 @@
<string name="cant_open_last_read_chapter">Nu se poate deschide ultimul capitol citit</string> <string name="cant_open_last_read_chapter">Nu se poate deschide ultimul capitol citit</string>
<string name="extension_api_error">Nu s-a putut obține lista de extensii</string> <string name="extension_api_error">Nu s-a putut obține lista de extensii</string>
<string name="ext_install_service_notif">Se instalează extensia…</string> <string name="ext_install_service_notif">Se instalează extensia…</string>
<string name="ext_installer_pref">Program instalare</string> <string name="ext_installer_pref">Program de instalare</string>
<string name="pref_create_folder_per_manga_summary">Creează dosare în funcție de titlul intrărilor</string> <string name="pref_create_folder_per_manga_summary">Creează dosare în funcție de titlul intrărilor</string>
<string name="automatic_background">Automat</string> <string name="automatic_background">Automat</string>
<string name="on">Pornit</string> <string name="on">Pornit</string>
@ -708,4 +707,61 @@
<string name="action_sort_next_updated">Următoarea actualizare așteptată</string> <string name="action_sort_next_updated">Următoarea actualizare așteptată</string>
<string name="action_filter_interval_long">Fetch lunar (28 de zile)</string> <string name="action_filter_interval_long">Fetch lunar (28 de zile)</string>
<string name="action_filter_interval_custom">Interval de preluare personalizat</string> <string name="action_filter_interval_custom">Interval de preluare personalizat</string>
<string name="pref_storage_location">Locație de stocare</string>
<string name="information_cloudflare_help">Atinge aici pentru ajutor cu Cloudflare</string>
<string name="action_create">Creați</string>
<string name="relative_time_span_never">Niciodată</string>
<string name="pref_library_columns_per_row">%d pe rând</string>
<string name="pref_flash_page_summ">Reduceți imaginile fantomă pe ecranele e-ink</string>
<string name="action_copy_to_clipboard">Copiați în clipboard</string>
<string name="pref_page_rotate">Rotiți paginile late pentru a se potrivi</string>
<string name="pref_storage_location_info">Folosit pentru copii de rezervă automate, capitole descărcate, și surse locale.</string>
<string name="action_apply">Aplică</string>
<string name="pref_debug_info">Informații de depanare</string>
<string name="syncing_library">Sincronizare bibliotecă</string>
<string name="create_backup_file_error">Copia de rezervă nu a putut fi creată</string>
<string name="intervals_header">Intervale</string>
<string name="action_revert_to_default">Reveniți la implicit</string>
<string name="action_sort_category">Sortează categoriile</string>
<string name="action_update_category">Actualizați categoria</string>
<string name="action_menu_overflow_description">Mai multe opțiuni</string>
<string name="library_sync_complete">Sincronizare bibliotecă completă</string>
<string name="last_auto_backup_info">Ultima copie de rezervă creata la: %s</string>
<string name="manga_modify_calculated_interval_title">Personalizați intervalul</string>
<string name="pref_page_rotate_invert">Răsturnați orientarea paginilor late rotite</string>
<string name="selected">Selectați</string>
<string name="no_scanlators_found">Nici un scanlator nu a fost găsit</string>
<string name="not_selected">Nu a fost selectat</string>
<string name="action_move_to_bottom_all_for_series">Mutați seria pe ultima poziție</string>
<string name="pref_chapter_swipe_end">Acțiune glisare către dreapta</string>
<string name="licensed_manga_chapters_error">Licențiat - Nu exista capitole pentru afișare</string>
<string name="exception_offline">Fără conexiune la internet</string>
<string name="pref_storage_usage">Utilizarea spațiului de stocare</string>
<string name="notification_updating_progress">Actualizare bibliotecă… (%s)</string>
<string name="download_cache_invalidated">Indexul de descărcări a fost invalidat</string>
<string name="action_bar_up_description">Navighează în sus</string>
<string name="action_sort_tracker_score">Scorul tracker-ului</string>
<string name="sort_category_confirmation">Ați dori să sortați categoriile alfabetic?</string>
<string name="skipped_reason_not_in_release_period">Sărit peste deoarece nici o lansare nu era așteptată astăzi</string>
<string name="file_null_uri_error">Nici o filă selectată</string>
<string name="track_delete_title">Eliminați monitorizarea %s?</string>
<string name="source_settings">Setări surse</string>
<string name="app_settings">Setări aplicație</string>
<string name="pref_chapter_swipe_start">Acțiune glisare către stânga</string>
<string name="track_delete_remote_text">De asemenea elimină din %s</string>
<string name="split_tall_images">Împărțiți imaginile înalte</string>
<string name="has_results">Au fost găsite rezultate</string>
<string name="track_delete_text">Această acțiune va elimina local monitorizarea.</string>
<string name="pref_update_only_in_release_period">În afara perioadei de lansare estimată</string>
<string name="action_ok">Ok</string>
<string name="pref_double_tap_zoom">Atingeți de două ori pentru a mări</string>
<string name="track_activity_name">Autentificare tracker</string>
<string name="pref_hide_in_library_items">Ascundeți înregistrările care se află deja în bibliotecă</string>
<string name="pref_relative_format">Marcaje de timp relative</string>
<string name="exception_http">HTTP %d, verificați site-ul in modul WebView</string>
<string name="pref_relative_format_summary">\"%1$s\" în loc de \"%2$s\"</string>
<string name="exception_unknown_host">Nu a putut fi accesat %s</string>
<string name="label_tracked_titles">Înregistrări monitorizate</string>
<string name="exclude_scanlators">Exclude scanlator</string>
<string name="pref_chapter_swipe">Glisare capitol</string>
</resources> </resources>

Some files were not shown because too many files have changed in this diff Show More