mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-20 15:29:18 +01:00
Merge branch 'master' into sync-part-final
This commit is contained in:
commit
3532074a70
@ -22,7 +22,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "eu.kanade.tachiyomi"
|
||||
|
||||
versionCode = 111
|
||||
versionCode = 112
|
||||
versionName = "0.14.7"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
|
@ -24,6 +24,8 @@ class BasePreferences(
|
||||
|
||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||
|
||||
fun shownOnboardingFlow() = preferenceStore.getBoolean(Preference.appStateKey("onboarding_complete"), false)
|
||||
|
||||
enum class ExtensionInstaller(val titleRes: StringResource) {
|
||||
LEGACY(MR.strings.ext_installer_legacy),
|
||||
PACKAGEINSTALLER(MR.strings.ext_installer_packageinstaller),
|
||||
|
@ -22,8 +22,8 @@ import tachiyomi.domain.chapter.service.ChapterRecognition
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.source.local.isLocal
|
||||
import java.lang.Long.max
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
import java.util.TreeSet
|
||||
|
||||
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
|
||||
// to a higher value than newer chapters
|
||||
|
@ -10,8 +10,8 @@ import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
|
||||
class UpdateManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
@ -46,14 +46,14 @@ class UpdateManga(
|
||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||
localManga.isLocal() -> Date().time
|
||||
localManga.isLocal() -> Instant.now().toEpochMilli()
|
||||
localManga.hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
Date().time
|
||||
Instant.now().toEpochMilli()
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,16 +87,16 @@ class UpdateManga(
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
val dateAdded = when (favorite) {
|
||||
true -> Date().time
|
||||
true -> Instant.now().toEpochMilli()
|
||||
false -> 0
|
||||
}
|
||||
return mangaRepository.update(
|
||||
|
@ -17,8 +17,7 @@ import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.track.interactor.GetTracks
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.toJavaDuration
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class DelayedTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
@ -63,7 +62,7 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
|
||||
|
||||
val request = OneTimeWorkRequestBuilder<DelayedTrackingUpdateJob>()
|
||||
.setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5.minutes.toJavaDuration())
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5, TimeUnit.MINUTES)
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
|
@ -14,6 +14,11 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.Button
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@ -62,6 +67,7 @@ fun ExtensionScreen(
|
||||
searchQuery: String?,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
onClickItemCancel: (Extension) -> Unit,
|
||||
onClickItemWebView: (Extension.Available) -> Unit,
|
||||
onInstallExtension: (Extension.Available) -> Unit,
|
||||
onUninstallExtension: (Extension) -> Unit,
|
||||
onUpdateExtension: (Extension.Installed) -> Unit,
|
||||
@ -94,6 +100,7 @@ fun ExtensionScreen(
|
||||
contentPadding = contentPadding,
|
||||
onLongClickItem = onLongClickItem,
|
||||
onClickItemCancel = onClickItemCancel,
|
||||
onClickItemWebView = onClickItemWebView,
|
||||
onInstallExtension = onInstallExtension,
|
||||
onUninstallExtension = onUninstallExtension,
|
||||
onUpdateExtension = onUpdateExtension,
|
||||
@ -111,6 +118,7 @@ private fun ExtensionContent(
|
||||
state: ExtensionsScreenModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
onClickItemWebView: (Extension.Available) -> Unit,
|
||||
onClickItemCancel: (Extension) -> Unit,
|
||||
onInstallExtension: (Extension.Available) -> Unit,
|
||||
onUninstallExtension: (Extension) -> Unit,
|
||||
@ -177,6 +185,7 @@ private fun ExtensionContent(
|
||||
}
|
||||
},
|
||||
onLongClickItem = onLongClickItem,
|
||||
onClickItemWebView = onClickItemWebView,
|
||||
onClickItemCancel = onClickItemCancel,
|
||||
onClickItemAction = {
|
||||
when (it) {
|
||||
@ -217,6 +226,7 @@ private fun ExtensionItem(
|
||||
item: ExtensionUiModel.Item,
|
||||
onClickItem: (Extension) -> Unit,
|
||||
onLongClickItem: (Extension) -> Unit,
|
||||
onClickItemWebView: (Extension.Available) -> Unit,
|
||||
onClickItemCancel: (Extension) -> Unit,
|
||||
onClickItemAction: (Extension) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@ -260,6 +270,7 @@ private fun ExtensionItem(
|
||||
ExtensionItemActions(
|
||||
extension = extension,
|
||||
installStep = installStep,
|
||||
onClickItemWebView = onClickItemWebView,
|
||||
onClickItemCancel = onClickItemCancel,
|
||||
onClickItemAction = onClickItemAction,
|
||||
)
|
||||
@ -343,42 +354,80 @@ private fun ExtensionItemActions(
|
||||
extension: Extension,
|
||||
installStep: InstallStep,
|
||||
modifier: Modifier = Modifier,
|
||||
onClickItemWebView: (Extension.Available) -> Unit = {},
|
||||
onClickItemCancel: (Extension) -> Unit = {},
|
||||
onClickItemAction: (Extension) -> Unit = {},
|
||||
) {
|
||||
val isIdle = installStep.isCompleted()
|
||||
Row(modifier = modifier) {
|
||||
if (isIdle) {
|
||||
TextButton(
|
||||
onClick = { onClickItemAction(extension) },
|
||||
) {
|
||||
Text(
|
||||
text = when (installStep) {
|
||||
InstallStep.Installed -> stringResource(MR.strings.ext_installed)
|
||||
InstallStep.Error -> stringResource(MR.strings.action_retry)
|
||||
InstallStep.Idle -> {
|
||||
when (extension) {
|
||||
is Extension.Installed -> {
|
||||
if (extension.hasUpdate) {
|
||||
stringResource(MR.strings.ext_update)
|
||||
} else {
|
||||
stringResource(MR.strings.action_settings)
|
||||
}
|
||||
}
|
||||
is Extension.Untrusted -> stringResource(MR.strings.ext_trust)
|
||||
is Extension.Available -> stringResource(MR.strings.ext_install)
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
when {
|
||||
!isIdle -> {
|
||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = stringResource(MR.strings.action_cancel),
|
||||
)
|
||||
}
|
||||
}
|
||||
installStep == InstallStep.Error -> {
|
||||
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Refresh,
|
||||
contentDescription = stringResource(MR.strings.action_retry),
|
||||
)
|
||||
}
|
||||
}
|
||||
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")
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
IconButton(onClick = { onClickItemCancel(extension) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = stringResource(MR.strings.action_cancel),
|
||||
)
|
||||
|
||||
IconButton(onClick = { onClickItemAction(extension) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Settings,
|
||||
contentDescription = stringResource(MR.strings.action_settings),
|
||||
)
|
||||
}
|
||||
}
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package eu.kanade.presentation.crash
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.BugReport
|
||||
@ -47,7 +47,7 @@ fun CrashScreen(
|
||||
modifier = Modifier
|
||||
.padding(vertical = MaterialTheme.padding.small)
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.fillMaxWidth()
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
) {
|
||||
Text(
|
||||
|
@ -96,7 +96,7 @@ fun MangaScreen(
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onTrackingClicked: () -> Unit,
|
||||
|
||||
// For tags menu
|
||||
onTagSearch: (String) -> Unit,
|
||||
@ -229,7 +229,7 @@ private fun MangaScreenSmallImpl(
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onTrackingClicked: () -> Unit,
|
||||
|
||||
// For tags menu
|
||||
onTagSearch: (String) -> Unit,
|
||||
@ -481,7 +481,7 @@ fun MangaScreenLargeImpl(
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onTrackingClicked: () -> Unit,
|
||||
|
||||
// For tags menu
|
||||
onTagSearch: (String) -> Unit,
|
||||
|
@ -92,7 +92,6 @@ private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTIL
|
||||
|
||||
@Composable
|
||||
fun MangaInfoBox(
|
||||
modifier: Modifier = Modifier,
|
||||
isTabletUi: Boolean,
|
||||
appBarPadding: Dp,
|
||||
title: String,
|
||||
@ -104,6 +103,7 @@ fun MangaInfoBox(
|
||||
status: Long,
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
// Backdrop
|
||||
@ -162,7 +162,6 @@ fun MangaInfoBox(
|
||||
|
||||
@Composable
|
||||
fun MangaActionRow(
|
||||
modifier: Modifier = Modifier,
|
||||
favorite: Boolean,
|
||||
trackingCount: Int,
|
||||
fetchInterval: Int?,
|
||||
@ -170,9 +169,10 @@ fun MangaActionRow(
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onWebViewLongClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onTrackingClicked: () -> Unit,
|
||||
onEditIntervalClicked: (() -> Unit)?,
|
||||
onEditCategory: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
||||
|
||||
@ -200,18 +200,16 @@ fun MangaActionRow(
|
||||
onClick = onEditIntervalClicked,
|
||||
)
|
||||
}
|
||||
if (onTrackingClicked != null) {
|
||||
MangaActionButton(
|
||||
title = if (trackingCount == 0) {
|
||||
stringResource(MR.strings.manga_tracking_tab)
|
||||
} else {
|
||||
pluralStringResource(MR.plurals.num_trackers, count = trackingCount, trackingCount)
|
||||
},
|
||||
icon = if (trackingCount == 0) Icons.Outlined.Sync else Icons.Outlined.Done,
|
||||
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
||||
onClick = onTrackingClicked,
|
||||
)
|
||||
}
|
||||
MangaActionButton(
|
||||
title = if (trackingCount == 0) {
|
||||
stringResource(MR.strings.manga_tracking_tab)
|
||||
} else {
|
||||
pluralStringResource(MR.plurals.num_trackers, count = trackingCount, trackingCount)
|
||||
},
|
||||
icon = if (trackingCount == 0) Icons.Outlined.Sync else Icons.Outlined.Done,
|
||||
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
||||
onClick = onTrackingClicked,
|
||||
)
|
||||
if (onWebViewClicked != null) {
|
||||
MangaActionButton(
|
||||
title = stringResource(MR.strings.action_web_view),
|
||||
@ -226,12 +224,12 @@ fun MangaActionRow(
|
||||
|
||||
@Composable
|
||||
fun ExpandableMangaDescription(
|
||||
modifier: Modifier = Modifier,
|
||||
defaultExpandState: Boolean,
|
||||
description: String?,
|
||||
tagsProvider: () -> List<String>?,
|
||||
onTagSearch: (String) -> Unit,
|
||||
onCopyTagToClipboard: (tag: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
val (expanded, onExpanded) = rememberSaveable {
|
||||
@ -406,13 +404,13 @@ private fun MangaAndSourceTitlesSmall(
|
||||
@Composable
|
||||
private fun MangaContentInfo(
|
||||
title: String,
|
||||
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
author: String?,
|
||||
artist: String?,
|
||||
status: Long,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Text(
|
||||
@ -556,7 +554,10 @@ private fun MangaSummary(
|
||||
expanded: Boolean,
|
||||
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(
|
||||
modifier = modifier.clipToBounds(),
|
||||
contents = listOf(
|
||||
|
@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
@ -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]()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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) },
|
||||
)
|
||||
}
|
||||
}
|
@ -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_QUAD9
|
||||
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.system.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
@ -110,6 +111,10 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||
title = stringResource(MR.strings.pref_debug_info),
|
||||
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) {
|
||||
|
@ -2,8 +2,8 @@ package eu.kanade.presentation.more.settings.screen
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.setAppCompatDelegateThemeMode
|
||||
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.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
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 tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
@ -33,7 +31,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.util.collectAsState
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
|
||||
object SettingsAppearanceScreen : SearchableSettings {
|
||||
|
||||
@ -43,72 +41,59 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
|
||||
@Composable
|
||||
override fun getPreferences(): List<Preference> {
|
||||
val context = LocalContext.current
|
||||
val uiPreferences = remember { Injekt.get<UiPreferences>() }
|
||||
|
||||
return listOf(
|
||||
getThemeGroup(context = context, uiPreferences = uiPreferences),
|
||||
getDisplayGroup(context = context, uiPreferences = uiPreferences),
|
||||
getThemeGroup(uiPreferences = uiPreferences),
|
||||
getDisplayGroup(uiPreferences = uiPreferences),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getThemeGroup(
|
||||
context: Context,
|
||||
uiPreferences: UiPreferences,
|
||||
): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
|
||||
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()
|
||||
|
||||
LaunchedEffect(themeMode) {
|
||||
setAppCompatDelegateThemeMode(themeMode)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
merge(appThemePref.changes(), amoledPref.changes())
|
||||
.drop(2)
|
||||
.collectLatest { (context as? Activity)?.let { ActivityCompat.recreate(it) } }
|
||||
}
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(MR.strings.pref_category_theme),
|
||||
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(
|
||||
title = stringResource(MR.strings.pref_app_theme),
|
||||
) { item ->
|
||||
val value by appThemePref.collectAsState()
|
||||
AppThemePreferenceWidget(
|
||||
title = item.title,
|
||||
value = value,
|
||||
amoled = amoled,
|
||||
onItemClick = { appThemePref.set(it) },
|
||||
)
|
||||
) {
|
||||
Column {
|
||||
AppThemeModePreferenceWidget(
|
||||
value = themeMode,
|
||||
onItemClick = {
|
||||
themeModePref.set(it)
|
||||
setAppCompatDelegateThemeMode(it)
|
||||
},
|
||||
)
|
||||
|
||||
AppThemePreferenceWidget(
|
||||
value = appTheme,
|
||||
amoled = amoled,
|
||||
onItemClick = { appThemePref.set(it) },
|
||||
)
|
||||
}
|
||||
},
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = amoledPref,
|
||||
title = stringResource(MR.strings.pref_dark_theme_pure_black),
|
||||
enabled = themeMode != ThemeMode.LIGHT,
|
||||
onValueChanged = {
|
||||
(context as? Activity)?.let { ActivityCompat.recreate(it) }
|
||||
true
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -116,14 +101,15 @@ object SettingsAppearanceScreen : SearchableSettings {
|
||||
|
||||
@Composable
|
||||
private fun getDisplayGroup(
|
||||
context: Context,
|
||||
uiPreferences: UiPreferences,
|
||||
): Preference.PreferenceGroup {
|
||||
val context = LocalContext.current
|
||||
|
||||
val langs = remember { getLangs(context) }
|
||||
var currentLanguage by remember {
|
||||
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")
|
||||
}
|
||||
val now = remember { Date().time }
|
||||
val now = remember { Instant.now().toEpochMilli() }
|
||||
|
||||
val dateFormat by uiPreferences.dateFormat().collectAsState()
|
||||
val formattedNow = remember(dateFormat) {
|
||||
|
@ -7,6 +7,7 @@ import android.net.Uri
|
||||
import android.os.Environment
|
||||
import android.text.format.Formatter
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@ -89,13 +90,12 @@ object SettingsDataScreen : SearchableSettings {
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getStorageLocationPref(
|
||||
storagePreferences: StoragePreferences,
|
||||
): Preference.PreferenceItem.TextPreference {
|
||||
fun storageLocationPicker(
|
||||
storageDirPref: tachiyomi.core.preference.Preference<String>,
|
||||
): ManagedActivityResultLauncher<Uri?, Uri?> {
|
||||
val context = LocalContext.current
|
||||
val storageDirPref = storagePreferences.baseStorageDirectory()
|
||||
val storageDir by storageDirPref.collectAsState()
|
||||
val pickStorageLocation = rememberLauncherForActivityResult(
|
||||
|
||||
return rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocumentTree(),
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
@ -110,13 +110,35 @@ object SettingsDataScreen : SearchableSettings {
|
||||
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(
|
||||
title = stringResource(MR.strings.pref_storage_location),
|
||||
subtitle = remember(storageDir) {
|
||||
val file = UniFile.fromUri(context, storageDir.toUri())
|
||||
file?.filePath ?: file?.uri?.toString()
|
||||
} ?: stringResource(MR.strings.invalid_location, storageDir),
|
||||
subtitle = storageLocationText(storagePreferences.baseStorageDirectory()),
|
||||
onClick = {
|
||||
try {
|
||||
pickStorageLocation.launch(null)
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.presentation.more.settings.widget
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
@ -36,9 +37,11 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.app.ActivityCompat
|
||||
import eu.kanade.domain.ui.model.AppTheme
|
||||
import eu.kanade.presentation.manga.components.MangaCover
|
||||
import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
@ -51,13 +54,11 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||
|
||||
@Composable
|
||||
internal fun AppThemePreferenceWidget(
|
||||
title: String,
|
||||
value: AppTheme,
|
||||
amoled: Boolean,
|
||||
onItemClick: (AppTheme) -> Unit,
|
||||
) {
|
||||
BasePreferenceWidget(
|
||||
title = title,
|
||||
subcomponent = {
|
||||
AppThemesList(
|
||||
currentTheme = value,
|
||||
@ -74,6 +75,7 @@ private fun AppThemesList(
|
||||
amoled: Boolean,
|
||||
onItemClick: (AppTheme) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val appThemes = remember {
|
||||
AppTheme.entries
|
||||
.filterNot { it.titleRes == null || (it == AppTheme.MONET && !DeviceUtil.isDynamicColorAvailable) }
|
||||
@ -97,7 +99,10 @@ private fun AppThemesList(
|
||||
) {
|
||||
AppThemePreviewItem(
|
||||
selected = currentTheme == appTheme,
|
||||
onClick = { onItemClick(appTheme) },
|
||||
onClick = {
|
||||
onItemClick(appTheme)
|
||||
(context as? Activity)?.let { ActivityCompat.recreate(it) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import androidx.compose.runtime.ReadOnlyComposable
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@ -29,7 +29,7 @@ fun Duration.toDurationString(context: Context, fallback: String): String {
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
fun relativeTimeSpanString(epochMillis: Long): String {
|
||||
val now = Date().time
|
||||
val now = Instant.now().toEpochMilli()
|
||||
return when {
|
||||
epochMillis <= 0L -> stringResource(MR.strings.relative_time_span_never)
|
||||
now - epochMillis < 1.minutes.inWholeMilliseconds -> stringResource(
|
||||
|
@ -396,7 +396,12 @@ object Migrations {
|
||||
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(
|
||||
"pref_download_only",
|
||||
"incognito_mode",
|
||||
@ -409,6 +414,7 @@ object Migrations {
|
||||
"last_app_check",
|
||||
"last_ext_check",
|
||||
"last_version_code",
|
||||
"storage_dir",
|
||||
)
|
||||
replacePreferences(
|
||||
preferenceStore = preferenceStore,
|
||||
@ -416,11 +422,6 @@ object Migrations {
|
||||
newKey = { Preference.appStateKey(it) },
|
||||
)
|
||||
}
|
||||
if (oldVersion < 111) {
|
||||
File(context.cacheDir, "dl_index_cache")
|
||||
.takeIf { it.exists() }
|
||||
?.delete()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -25,10 +25,8 @@ import tachiyomi.domain.backup.service.BackupPreferences
|
||||
import tachiyomi.domain.storage.service.StorageManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
class BackupCreateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
@ -52,7 +50,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
|
||||
return try {
|
||||
val location = BackupCreator(context).createBackup(uri, flags, isAutoBackup)
|
||||
if (isAutoBackup) {
|
||||
backupPreferences.lastAutoBackupTimestamp().set(Date().time)
|
||||
backupPreferences.lastAutoBackupTimestamp().set(Instant.now().toEpochMilli())
|
||||
} else {
|
||||
notifier.showBackupComplete(UniFile.fromUri(context, location.toUri())!!)
|
||||
}
|
||||
@ -97,7 +95,7 @@ class BackupCreateJob(private val context: Context, workerParams: WorkerParamete
|
||||
10,
|
||||
TimeUnit.MINUTES,
|
||||
)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10.minutes.toJavaDuration())
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.MINUTES)
|
||||
.addTag(TAG_AUTO)
|
||||
.setConstraints(constraints)
|
||||
.setInputData(workDataOf(IS_AUTO_BACKUP_KEY to true))
|
||||
|
@ -21,7 +21,7 @@ import eu.kanade.tachiyomi.source.sourcePreferences
|
||||
import eu.kanade.tachiyomi.util.BackupUtil
|
||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import tachiyomi.core.i18n.stringResource
|
||||
import tachiyomi.core.preference.AndroidPreferenceStore
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
@ -76,14 +76,12 @@ class BackupRestorer(
|
||||
|
||||
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()
|
||||
restoreProgress = 0
|
||||
errors.clear()
|
||||
|
||||
if (!performRestore(uri, sync)) {
|
||||
return false
|
||||
}
|
||||
performRestore(uri, sync)
|
||||
|
||||
val endTime = System.currentTimeMillis()
|
||||
val time = endTime - startTime
|
||||
@ -95,7 +93,6 @@ class BackupRestorer(
|
||||
} else {
|
||||
notifier.showRestoreComplete(time, errors.size, logFile.parent, logFile.name)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun writeErrorLog(): File {
|
||||
@ -117,74 +114,57 @@ class BackupRestorer(
|
||||
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)
|
||||
|
||||
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
|
||||
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
|
||||
sourceMapping = backupMaps.associate { it.sourceId to it.name }
|
||||
now = ZonedDateTime.now()
|
||||
currentFetchWindow = fetchInterval.getWindow(now)
|
||||
|
||||
return coroutineScope {
|
||||
coroutineScope {
|
||||
ensureActive()
|
||||
restoreCategories(backup.backupCategories)
|
||||
|
||||
ensureActive()
|
||||
restoreAppPreferences(backup.backupPreferences)
|
||||
|
||||
ensureActive()
|
||||
restoreSourcePreferences(backup.backupSourcePreferences)
|
||||
|
||||
// Restore individual manga
|
||||
backup.backupManga.forEach {
|
||||
if (!isActive) {
|
||||
return@coroutineScope false
|
||||
}
|
||||
|
||||
ensureActive()
|
||||
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>) {
|
||||
// Get categories from file and from db
|
||||
val dbCategories = getCategories.await()
|
||||
if (backupCategories.isNotEmpty()) {
|
||||
val dbCategories = getCategories.await()
|
||||
val dbCategoriesByName = dbCategories.associateBy { it.name }
|
||||
|
||||
val categories = backupCategories.map {
|
||||
var category = it.getCategory()
|
||||
var found = false
|
||||
for (dbCategory in dbCategories) {
|
||||
// If the category is already in the db, assign the id to the file's category
|
||||
// and do nothing
|
||||
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)
|
||||
val categories = backupCategories.map {
|
||||
dbCategoriesByName[it.name]
|
||||
?: handler.awaitOneExecutable {
|
||||
categoriesQueries.insert(it.name, it.order, it.flags)
|
||||
categoriesQueries.selectLastInsertedRowId()
|
||||
}.let { id -> it.toCategory(id) }
|
||||
}
|
||||
|
||||
category
|
||||
libraryPreferences.categorizedDisplaySettings().set(
|
||||
(dbCategories + categories)
|
||||
.distinctBy { it.flags }
|
||||
.size > 1,
|
||||
)
|
||||
}
|
||||
|
||||
libraryPreferences.categorizedDisplaySettings().set(
|
||||
(dbCategories + categories)
|
||||
.distinctBy { it.flags }
|
||||
.size > 1,
|
||||
)
|
||||
|
||||
restoreProgress += 1
|
||||
showRestoreProgress(
|
||||
restoreProgress,
|
||||
|
@ -11,14 +11,12 @@ class BackupCategory(
|
||||
// @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x
|
||||
@ProtoNumber(100) var flags: Long = 0,
|
||||
) {
|
||||
fun getCategory(): Category {
|
||||
return Category(
|
||||
id = 0,
|
||||
name = this@BackupCategory.name,
|
||||
flags = this@BackupCategory.flags,
|
||||
order = this@BackupCategory.order,
|
||||
)
|
||||
}
|
||||
fun toCategory(id: Long) = Category(
|
||||
id = id,
|
||||
name = this@BackupCategory.name,
|
||||
flags = this@BackupCategory.flags,
|
||||
order = this@BackupCategory.order,
|
||||
)
|
||||
}
|
||||
|
||||
val backupCategoryMapper = { category: Category ->
|
||||
|
@ -47,7 +47,6 @@ import tachiyomi.core.storage.extension
|
||||
import tachiyomi.core.util.lang.launchIO
|
||||
import tachiyomi.core.util.lang.launchNow
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.lang.withUIContext
|
||||
import tachiyomi.core.util.system.ImageUtil
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
@ -265,24 +264,21 @@ class Downloader(
|
||||
* @param chapters the list of chapters to download.
|
||||
* @param autoStart whether to start the downloader after enqueing the chapters.
|
||||
*/
|
||||
fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) = launchIO {
|
||||
if (chapters.isEmpty()) {
|
||||
return@launchIO
|
||||
}
|
||||
fun queueChapters(manga: Manga, chapters: List<Chapter>, autoStart: Boolean) {
|
||||
if (chapters.isEmpty()) return
|
||||
|
||||
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 chaptersWithoutDir = chapters
|
||||
val chaptersToQueue = chapters.asSequence()
|
||||
// Filter out those already downloaded.
|
||||
.filter { provider.findChapterDir(it.name, it.scanlator, manga.title, source) == null }
|
||||
// Add chapters to queue from the start.
|
||||
.sortedByDescending { it.sourceOrder }
|
||||
|
||||
val chaptersToQueue = chaptersWithoutDir
|
||||
// Filter out those already enqueued.
|
||||
.filter { chapter -> queueState.value.none { it.chapter.id == chapter.id } }
|
||||
// Create a download for each one.
|
||||
.map { Download(source, manga, it) }
|
||||
.toList()
|
||||
|
||||
if (chaptersToQueue.isNotEmpty()) {
|
||||
addAllToQueue(chaptersToQueue)
|
||||
@ -299,13 +295,11 @@ class Downloader(
|
||||
queuedDownloads > DOWNLOADS_QUEUED_WARNING_THRESHOLD ||
|
||||
maxDownloadsFromSource > CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD
|
||||
) {
|
||||
withUIContext {
|
||||
notifier.onWarning(
|
||||
context.stringResource(MR.strings.download_queue_size_warning),
|
||||
WARNING_NOTIF_TIMEOUT_MS,
|
||||
NotificationHandler.openUrl(context, LibraryUpdateNotifier.HELP_WARNING_URL),
|
||||
)
|
||||
}
|
||||
notifier.onWarning(
|
||||
context.stringResource(MR.strings.download_queue_size_warning),
|
||||
WARNING_NOTIF_TIMEOUT_MS,
|
||||
NotificationHandler.openUrl(context, LibraryUpdateNotifier.HELP_WARNING_URL),
|
||||
)
|
||||
}
|
||||
DownloadJob.start(context)
|
||||
}
|
||||
|
@ -69,8 +69,8 @@ import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@ -111,7 +111,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
|
||||
setForegroundSafely()
|
||||
|
||||
libraryPreferences.lastUpdatedTimestamp().set(Date().time)
|
||||
libraryPreferences.lastUpdatedTimestamp().set(Instant.now().toEpochMilli())
|
||||
|
||||
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
|
||||
addMangaToQueue(categoryId)
|
||||
|
@ -7,7 +7,6 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.core.net.toUri
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
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.ui.main.MainActivity
|
||||
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.getParcelableExtraCompat
|
||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||
@ -66,12 +64,6 @@ class NotificationReceiver : BroadcastReceiver() {
|
||||
context,
|
||||
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
|
||||
ACTION_SHARE_BACKUP ->
|
||||
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.
|
||||
*
|
||||
@ -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.
|
||||
*
|
||||
|
@ -23,7 +23,7 @@ import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
|
||||
class ImageSaver(
|
||||
val context: Context,
|
||||
@ -79,7 +79,7 @@ class ImageSaver(
|
||||
MediaStore.Images.Media.RELATIVE_PATH to relativePath,
|
||||
MediaStore.Images.Media.DISPLAY_NAME to image.name,
|
||||
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) {
|
||||
|
@ -26,7 +26,10 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
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
|
||||
|
||||
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 {
|
||||
return try {
|
||||
val date = Calendar.getInstance()
|
||||
date.set(
|
||||
struct[dateKey]!!.jsonObject["year"]!!.jsonPrimitive.int,
|
||||
struct[dateKey]!!.jsonObject["month"]!!.jsonPrimitive.int - 1,
|
||||
struct[dateKey]!!.jsonObject["day"]!!.jsonPrimitive.int,
|
||||
)
|
||||
date.timeInMillis
|
||||
return LocalDate
|
||||
.of(
|
||||
struct[dateKey]!!.jsonObject["year"]!!.jsonPrimitive.int,
|
||||
struct[dateKey]!!.jsonObject["month"]!!.jsonPrimitive.int,
|
||||
struct[dateKey]!!.jsonObject["day"]!!.jsonPrimitive.int,
|
||||
)
|
||||
.atStartOfDay(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.toEpochMilli()
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}
|
||||
@ -349,12 +354,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
}
|
||||
}
|
||||
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = dateValue
|
||||
val dateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(dateValue), ZoneId.systemDefault())
|
||||
return buildJsonObject {
|
||||
put("year", calendar.get(Calendar.YEAR))
|
||||
put("month", calendar.get(Calendar.MONTH) + 1)
|
||||
put("day", calendar.get(Calendar.DAY_OF_MONTH))
|
||||
put("year", dateTime.year)
|
||||
put("month", dateTime.monthValue)
|
||||
put("day", dateTime.dayOfMonth)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
internal class ExtensionGithubApi {
|
||||
@ -76,14 +76,16 @@ internal class ExtensionGithubApi {
|
||||
fromAvailableExtensionList: Boolean = false,
|
||||
): List<Extension.Installed>? {
|
||||
// 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
|
||||
}
|
||||
|
||||
val extensions = if (fromAvailableExtensionList) {
|
||||
extensionManager.availableExtensionsFlow.value
|
||||
} else {
|
||||
findExtensions().also { lastExtCheck.set(Date().time) }
|
||||
findExtensions().also { lastExtCheck.set(Instant.now().toEpochMilli()) }
|
||||
}
|
||||
|
||||
val installedExtensions = ExtensionLoader.loadExtensions(context)
|
||||
|
@ -12,6 +12,7 @@ import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.TabContent
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreen
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewScreen
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
@ -47,6 +48,17 @@ fun extensionsTab(
|
||||
},
|
||||
onClickItemCancel = extensionsScreenModel::cancelInstallUpdateExtension,
|
||||
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,
|
||||
onOpenExtension = { navigator.push(ExtensionDetailsScreen(it.pkgName)) },
|
||||
onTrustExtension = { extensionsScreenModel.trustSignature(it.signatureHash) },
|
||||
|
@ -53,7 +53,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
|
||||
@Composable
|
||||
internal fun MigrateDialog(
|
||||
@ -298,7 +298,7 @@ internal class MigrateDialogScreenModel(
|
||||
favorite = true,
|
||||
chapterFlags = oldManga.chapterFlags,
|
||||
viewerFlags = oldManga.viewerFlags,
|
||||
dateAdded = if (replace) oldManga.dateAdded else Date().time,
|
||||
dateAdded = if (replace) oldManga.dateAdded else Instant.now().toEpochMilli(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ import tachiyomi.domain.source.interactor.GetRemoteManga
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Date
|
||||
import java.time.Instant
|
||||
import eu.kanade.tachiyomi.source.model.Filter as SourceModelFilter
|
||||
|
||||
class BrowseSourceScreenModel(
|
||||
@ -225,7 +225,7 @@ class BrowseSourceScreenModel(
|
||||
favorite = !manga.favorite,
|
||||
dateAdded = when (manga.favorite) {
|
||||
true -> 0
|
||||
false -> Date().time
|
||||
false -> Instant.now().toEpochMilli()
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -126,12 +126,12 @@ object HomeScreen : Screen() {
|
||||
materialFadeThroughIn(initialScale = 1f, durationMillis = TabFadeDuration) togetherWith
|
||||
materialFadeThroughOut(durationMillis = TabFadeDuration)
|
||||
},
|
||||
content = {
|
||||
tabNavigator.saveableState(key = "currentTab", it) {
|
||||
it.Content()
|
||||
}
|
||||
},
|
||||
)
|
||||
label = "tabContent",
|
||||
) {
|
||||
tabNavigator.saveableState(key = "currentTab", it) {
|
||||
it.Content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import eu.kanade.presentation.library.LibrarySettingsDialog
|
||||
import eu.kanade.presentation.library.components.LibraryContent
|
||||
import eu.kanade.presentation.library.components.LibraryToolbar
|
||||
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.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||
@ -163,7 +164,7 @@ object LibraryTab : Tab {
|
||||
EmptyScreenAction(
|
||||
stringRes = MR.strings.getting_started_guide,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = { handler.openUri("https://tachiyomi.org/docs/guides/getting-started") },
|
||||
onClick = { handler.openUri(GETTING_STARTED_URL) },
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -73,6 +73,7 @@ import eu.kanade.tachiyomi.ui.deeplink.DeepLinkScreen
|
||||
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
||||
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.isNavigationBarNeedsScrim
|
||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||
@ -251,6 +252,7 @@ class MainActivity : BaseActivity() {
|
||||
HandleOnNewIntent(context = context, navigator = navigator)
|
||||
|
||||
CheckForUpdates()
|
||||
ShowOnboarding()
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
|
@ -47,6 +47,7 @@ import eu.kanade.tachiyomi.ui.category.CategoryScreen
|
||||
import eu.kanade.tachiyomi.ui.home.HomeScreen
|
||||
import eu.kanade.tachiyomi.ui.manga.track.TrackInfoDialogHomeScreen
|
||||
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.util.system.copyToClipboard
|
||||
import eu.kanade.tachiyomi.util.system.toShareIntent
|
||||
@ -130,7 +131,13 @@ class MangaScreen(
|
||||
screenModel.source,
|
||||
)
|
||||
}.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!!) } },
|
||||
onFilterButtonClicked = screenModel::showSettingsDialog,
|
||||
onRefresh = screenModel::fetchAllFromSource,
|
||||
|
@ -1090,9 +1090,6 @@ class MangaScreenModel(
|
||||
val filterActive: Boolean
|
||||
get() = scanlatorFilterActive || manga.chaptersFiltered()
|
||||
|
||||
val trackingAvailable: Boolean
|
||||
get() = trackItems.isNotEmpty()
|
||||
|
||||
val trackingCount: Int
|
||||
get() = trackItems.count { it.track != null }
|
||||
|
||||
|
@ -53,7 +53,7 @@ object MoreTab : Tab {
|
||||
}
|
||||
|
||||
override suspend fun onReselect(navigator: Navigator) {
|
||||
navigator.push(SettingsScreen.toMainScreen())
|
||||
navigator.push(SettingsScreen())
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ -72,9 +72,9 @@ object MoreTab : Tab {
|
||||
onClickDownloadQueue = { navigator.push(DownloadQueueScreen) },
|
||||
onClickCategories = { navigator.push(CategoryScreen()) },
|
||||
onClickStats = { navigator.push(StatsScreen()) },
|
||||
onClickDataAndStorage = { navigator.push(SettingsScreen.toDataAndStorageScreen()) },
|
||||
onClickSettings = { navigator.push(SettingsScreen.toMainScreen()) },
|
||||
onClickAbout = { navigator.push(SettingsScreen.toAboutScreen()) },
|
||||
onClickDataAndStorage = { navigator.push(SettingsScreen(SettingsScreen.Destination.DataAndStorage)) },
|
||||
onClickSettings = { navigator.push(SettingsScreen()) },
|
||||
onClickAbout = { navigator.push(SettingsScreen(SettingsScreen.Destination.About)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -74,6 +74,7 @@ import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
@ -539,7 +540,7 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun restartReadTimer() {
|
||||
chapterReadStartTime = Date().time
|
||||
chapterReadStartTime = Instant.now().toEpochMilli()
|
||||
}
|
||||
|
||||
fun flushReadTimer() {
|
||||
|
@ -83,12 +83,6 @@ class SaveImageNotifier(private val context: Context) {
|
||||
context.stringResource(MR.strings.action_share),
|
||||
NotificationReceiver.shareImagePendingBroadcast(context, uri),
|
||||
)
|
||||
// Delete action
|
||||
addAction(
|
||||
R.drawable.ic_delete_24dp,
|
||||
context.stringResource(MR.strings.action_delete),
|
||||
NotificationReceiver.deleteImagePendingBroadcast(context, uri),
|
||||
)
|
||||
|
||||
updateNotification()
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import eu.kanade.presentation.theme.TachiyomiTheme
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.source.local.isLocal
|
||||
|
||||
class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
AbstractComposeView(context, attrs) {
|
||||
@ -31,7 +32,7 @@ class ReaderTransitionView @JvmOverloads constructor(context: Context, attrs: At
|
||||
Data(
|
||||
transition = transition,
|
||||
currChapterDownloaded = transition.from.pageLoader?.isLocal == true,
|
||||
goingToChapterDownloaded = transition.to?.chapter?.let { goingToChapter ->
|
||||
goingToChapterDownloaded = manga.isLocal() || transition.to?.chapter?.let { goingToChapter ->
|
||||
downloadManager.isChapterDownloaded(
|
||||
chapterName = goingToChapter.name,
|
||||
chapterScanlator = goingToChapter.scanlator,
|
||||
|
@ -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.SettingsDataScreen
|
||||
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.util.DefaultNavigatorScreenTransition
|
||||
import eu.kanade.presentation.util.LocalBackPress
|
||||
@ -22,22 +23,22 @@ import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.presentation.util.isTabletUi
|
||||
import tachiyomi.presentation.core.components.TwoPanelBox
|
||||
|
||||
class SettingsScreen private constructor(
|
||||
val toDataAndStorage: Boolean,
|
||||
val toAbout: Boolean,
|
||||
class SettingsScreen(
|
||||
private val destination: Int? = null,
|
||||
) : Screen() {
|
||||
|
||||
constructor(destination: Destination) : this(destination.id)
|
||||
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val parentNavigator = LocalNavigator.currentOrThrow
|
||||
if (!isTabletUi()) {
|
||||
Navigator(
|
||||
screen = if (toDataAndStorage) {
|
||||
SettingsDataScreen
|
||||
} else if (toAbout) {
|
||||
AboutScreen
|
||||
} else {
|
||||
SettingsMainScreen
|
||||
screen = when (destination) {
|
||||
Destination.About.id -> AboutScreen
|
||||
Destination.DataAndStorage.id -> SettingsDataScreen
|
||||
Destination.Tracking.id -> SettingsTrackingScreen
|
||||
else -> SettingsMainScreen
|
||||
},
|
||||
content = {
|
||||
val pop: () -> Unit = {
|
||||
@ -54,12 +55,11 @@ class SettingsScreen private constructor(
|
||||
)
|
||||
} else {
|
||||
Navigator(
|
||||
screen = if (toDataAndStorage) {
|
||||
SettingsDataScreen
|
||||
} else if (toAbout) {
|
||||
AboutScreen
|
||||
} else {
|
||||
SettingsAppearanceScreen
|
||||
screen = when (destination) {
|
||||
Destination.About.id -> AboutScreen
|
||||
Destination.DataAndStorage.id -> SettingsDataScreen
|
||||
Destination.Tracking.id -> SettingsTrackingScreen
|
||||
else -> SettingsAppearanceScreen
|
||||
},
|
||||
) {
|
||||
val insets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal)
|
||||
@ -78,11 +78,9 @@ class SettingsScreen private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun toMainScreen() = SettingsScreen(toDataAndStorage = false, toAbout = false)
|
||||
|
||||
fun toDataAndStorageScreen() = SettingsScreen(toDataAndStorage = true, toAbout = false)
|
||||
|
||||
fun toAboutScreen() = SettingsScreen(toDataAndStorage = false, toAbout = true)
|
||||
sealed class Destination(val id: Int) {
|
||||
data object About : Destination(0)
|
||||
data object DataAndStorage : Destination(1)
|
||||
data object Tracking : Destination(2)
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ import tachiyomi.domain.updates.interactor.GetUpdates
|
||||
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Calendar
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
|
||||
class UpdatesScreenModel(
|
||||
@ -79,13 +79,10 @@ class UpdatesScreenModel(
|
||||
init {
|
||||
screenModelScope.launchIO {
|
||||
// Set date limit for recent chapters
|
||||
val calendar = Calendar.getInstance().apply {
|
||||
time = Date()
|
||||
add(Calendar.MONTH, -3)
|
||||
}
|
||||
val limit = ZonedDateTime.now().minusMonths(3).toInstant()
|
||||
|
||||
combine(
|
||||
getUpdates.subscribe(calendar).distinctUntilChanged(),
|
||||
getUpdates.subscribe(limit).distinctUntilChanged(),
|
||||
downloadCache.changes,
|
||||
downloadManager.queueState,
|
||||
) { updates, _, _ -> updates }
|
||||
|
@ -12,7 +12,7 @@ import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
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
|
||||
@ -28,7 +28,7 @@ fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSa
|
||||
|
||||
return when {
|
||||
isLocal() -> {
|
||||
this.copy(coverLastModified = Date().time)
|
||||
this.copy(coverLastModified = Instant.now().toEpochMilli())
|
||||
}
|
||||
hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(this, false)
|
||||
@ -36,7 +36,7 @@ fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSa
|
||||
}
|
||||
else -> {
|
||||
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 {
|
||||
if (isLocal()) return this
|
||||
return if (coverCache.deleteFromCache(this, true) > 0) {
|
||||
return copy(coverLastModified = Date().time)
|
||||
return copy(coverLastModified = Instant.now().toEpochMilli())
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import java.text.DateFormat
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
@ -38,13 +39,8 @@ fun Long.convertEpochMillisZone(
|
||||
* @return date as time key
|
||||
*/
|
||||
fun Long.toDateKey(): Date {
|
||||
val cal = Calendar.getInstance()
|
||||
cal.time = Date(this)
|
||||
cal[Calendar.HOUR_OF_DAY] = 0
|
||||
cal[Calendar.MINUTE] = 0
|
||||
cal[Calendar.SECOND] = 0
|
||||
cal[Calendar.MILLISECOND] = 0
|
||||
return cal.time
|
||||
val instant = Instant.ofEpochMilli(this)
|
||||
return Date.from(instant.truncatedTo(ChronoUnit.DAYS))
|
||||
}
|
||||
|
||||
private const val MILLISECONDS_IN_DAY = 86_400_000L
|
||||
|
@ -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>
|
@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.stateIn
|
||||
* Local-copy implementation of PreferenceStore mostly for test and preview purposes
|
||||
*/
|
||||
class InMemoryPreferenceStore(
|
||||
private val initialPreferences: Sequence<InMemoryPreference<*>> = sequenceOf(),
|
||||
initialPreferences: Sequence<InMemoryPreference<*>> = sequenceOf(),
|
||||
) : PreferenceStore {
|
||||
|
||||
private val preferences: Map<String, Preference<*>> =
|
||||
|
@ -1,5 +1,6 @@
|
||||
package tachiyomi.domain.storage.service
|
||||
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.storage.FolderProvider
|
||||
|
||||
@ -8,5 +9,5 @@ class StoragePreferences(
|
||||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun baseStorageDirectory() = preferenceStore.getString("storage_dir", folderProvider.path())
|
||||
fun baseStorageDirectory() = preferenceStore.getString(Preference.appStateKey("storage_dir"), folderProvider.path())
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package tachiyomi.domain.updates.interactor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||
import java.util.Calendar
|
||||
import java.time.Instant
|
||||
|
||||
class GetUpdates(
|
||||
private val repository: UpdatesRepository,
|
||||
@ -13,8 +13,8 @@ class GetUpdates(
|
||||
return repository.awaitWithRead(read, after, limit = 500)
|
||||
}
|
||||
|
||||
fun subscribe(calendar: Calendar): Flow<List<UpdatesWithRelations>> {
|
||||
return repository.subscribeAll(calendar.time.time, limit = 500)
|
||||
fun subscribe(instant: Instant): Flow<List<UpdatesWithRelations>> {
|
||||
return repository.subscribeAll(instant.toEpochMilli(), limit = 500)
|
||||
}
|
||||
|
||||
fun subscribe(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
compiler = "1.5.5"
|
||||
compiler = "1.5.6"
|
||||
compose-bom = "2023.12.00-alpha03"
|
||||
accompanist = "0.33.2-alpha"
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[versions]
|
||||
kotlin_version = "1.9.20"
|
||||
serialization_version = "1.6.1"
|
||||
kotlin_version = "1.9.21"
|
||||
serialization_version = "1.6.2"
|
||||
xml_serialization_version = "0.86.2"
|
||||
|
||||
[libraries]
|
||||
|
@ -1,13 +1,13 @@
|
||||
[versions]
|
||||
aboutlib_version = "10.9.2"
|
||||
leakcanary = "2.12"
|
||||
moko = "0.23.0"
|
||||
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"
|
||||
shizuku_version = "12.2.0"
|
||||
sqldelight = "2.0.0"
|
||||
sqlite = "2.4.0"
|
||||
voyager = "1.0.0"
|
||||
|
||||
[libraries]
|
||||
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"
|
||||
|
||||
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-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"]
|
||||
shizuku = ["shizuku-api", "shizuku-provider"]
|
||||
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"]
|
||||
test = ["junit", "kotest-assertions", "mockk"]
|
5
i18n/README.md
Normal file
5
i18n/README.md
Normal 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.
|
@ -54,7 +54,6 @@
|
||||
<string name="theme_dark">በርቷል</string>
|
||||
<string name="theme_light">ጠፍቷል</string>
|
||||
<string name="theme_system">ስርዓት ይከተሉ</string>
|
||||
<string name="pref_theme_mode">ጨለማ ሁነታ</string>
|
||||
<string name="pref_category_theme">ገጽታ</string>
|
||||
<string name="pref_category_about">ስለ</string>
|
||||
<string name="pref_category_advanced">የላቀ</string>
|
||||
|
@ -309,7 +309,6 @@
|
||||
<string name="theme_system">اتبع مظهر النظام</string>
|
||||
<string name="theme_dark">مفعّل</string>
|
||||
<string name="theme_light">غير مفعّل</string>
|
||||
<string name="pref_theme_mode">الوضع الليلي</string>
|
||||
<string name="battery_optimization_disabled">تم إلغاء وضع تحسين البطارية مُسبقاً</string>
|
||||
<string name="pref_disable_battery_optimization_summary">يساعد في عملية تحديث المكتبة والنسخ الإحتياطي في الخلفية</string>
|
||||
<string name="pref_disable_battery_optimization">إطفاء وضع تحسين البطارية</string>
|
||||
|
@ -174,6 +174,19 @@
|
||||
<!-- Shortcuts-->
|
||||
<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 -->
|
||||
<!-- Subsections -->
|
||||
<string name="pref_category_general">General</string>
|
||||
@ -198,11 +211,10 @@
|
||||
|
||||
<!-- General section -->
|
||||
<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="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_greenapple">Green Apple</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_bookmarked_chapters">Allow deleting bookmarked chapters</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="disabled">Disabled</string>
|
||||
<string name="last_read_chapter">Last read chapter</string>
|
||||
|
@ -150,7 +150,6 @@
|
||||
<string name="theme_dark">Уключаны</string>
|
||||
<string name="theme_light">Выключаны</string>
|
||||
<string name="theme_system">У адпаведнасці з сістэмнай тэмай</string>
|
||||
<string name="pref_theme_mode">Цёмны рэжым</string>
|
||||
<string name="pref_category_theme">Тэма</string>
|
||||
<string name="pref_category_about">Інфармацыя</string>
|
||||
<string name="pref_category_advanced">Дадаткова</string>
|
||||
|
@ -350,7 +350,6 @@
|
||||
<string name="theme_system">Система на абонаментите</string>
|
||||
<string name="theme_dark">Включено</string>
|
||||
<string name="theme_light">Изключено</string>
|
||||
<string name="pref_theme_mode">Тъмен режим</string>
|
||||
<string name="action_move_to_top">Премести най-горе</string>
|
||||
<string name="action_move_to_bottom">Премести най-долу</string>
|
||||
<string name="action_oldest">Най-стари</string>
|
||||
|
@ -315,7 +315,6 @@
|
||||
<string name="theme_dark">চালু করুন</string>
|
||||
<string name="theme_light">বন্ধ করুন</string>
|
||||
<string name="theme_system">সিস্টেমকে অনুসরণ করুন</string>
|
||||
<string name="pref_theme_mode">অন্ধকার মোড</string>
|
||||
<string name="pref_category_theme">থিম</string>
|
||||
<string name="action_move_to_bottom">নীচে সরান</string>
|
||||
<string name="action_move_to_top">শীর্ষে সরান</string>
|
||||
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">Darrer capítol</string>
|
||||
<string name="action_view_chapters">Mostra els capítols</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_dark">Activat</string>
|
||||
<string name="theme_system">Per defecte del sistema</string>
|
||||
|
@ -115,7 +115,6 @@
|
||||
<string name="pref_category_tracking">Pagsubay</string>
|
||||
<string name="pref_category_advanced">Abante</string>
|
||||
<string name="pref_category_theme">Tema</string>
|
||||
<string name="pref_theme_mode">Dark mode</string>
|
||||
<string name="theme_dark">Sa</string>
|
||||
<string name="pref_app_theme">Tema sa app</string>
|
||||
<string name="theme_monet">Dinamiko</string>
|
||||
|
@ -392,7 +392,6 @@
|
||||
<string name="lock_with_biometrics">Vyžadovat odemknutí</string>
|
||||
<string name="theme_dark">Zapnuto</string>
|
||||
<string name="theme_light">Vypnuto</string>
|
||||
<string name="pref_theme_mode">Temný vzhled</string>
|
||||
<string name="pref_category_theme">Vzhled</string>
|
||||
<string name="action_move_to_top">Přesunout nahoru</string>
|
||||
<string name="action_desc">Sestupně</string>
|
||||
|
@ -155,7 +155,6 @@
|
||||
<string name="action_open_log">Тӑвӑм-пулӑм кӗнекине уҫ</string>
|
||||
<string name="action_reset">Тасат</string>
|
||||
<string name="action_sort">Уйӑр</string>
|
||||
<string name="pref_theme_mode">Тӗксӗм темӑ</string>
|
||||
<string name="theme_system">Системри пекех</string>
|
||||
<string name="pref_category_reader">Вулӑш</string>
|
||||
<string name="pref_category_tracking">Йӗрлев</string>
|
||||
|
@ -126,7 +126,6 @@
|
||||
<string name="pref_category_advanced">Avanceret</string>
|
||||
<string name="pref_category_about">Om</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_light">Fra</string>
|
||||
<string name="theme_dark">Til</string>
|
||||
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">Neuestes Kapitel</string>
|
||||
<string name="action_view_chapters">Kapitel anzeigen</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_dark">An</string>
|
||||
<string name="theme_system">Systemeinstellung</string>
|
||||
|
@ -350,7 +350,6 @@
|
||||
<string name="theme_system">Ακολουθήστε το σύστημα</string>
|
||||
<string name="theme_dark">Ενεργοποιημένο</string>
|
||||
<string name="theme_light">Απενεργοποιημένο</string>
|
||||
<string name="pref_theme_mode">Σκοτεινή λειτουργία</string>
|
||||
<string name="action_move_to_bottom">Μετακίνηση στον πάτο</string>
|
||||
<string name="action_move_to_top">Μετακίνηση στην κορυφή</string>
|
||||
<string name="action_cancel_all">Ακύρωση όλων</string>
|
||||
|
@ -62,7 +62,6 @@
|
||||
<string name="theme_dark">Ŝalti</string>
|
||||
<string name="theme_light">Malŝalti</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_about">Pri</string>
|
||||
<string name="pref_category_downloads">Elŝutoj</string>
|
||||
|
@ -302,7 +302,6 @@
|
||||
<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_cancel_all">Cancelar todo</string>
|
||||
<string name="pref_theme_mode">Modo oscuro</string>
|
||||
<string name="theme_light">No</string>
|
||||
<string name="theme_dark">Sí</string>
|
||||
<string name="theme_system">Según ajustes del sistema</string>
|
||||
|
@ -492,7 +492,6 @@
|
||||
<string name="pref_category_downloads">Deskargak</string>
|
||||
<string name="pref_category_tracking">Jarraipena</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="theme_strawberrydaiquiri">Marrubi Daiquiri-a</string>
|
||||
<string name="theme_tako">Tako</string>
|
||||
|
@ -261,7 +261,6 @@
|
||||
<string name="theme_dark">روشن</string>
|
||||
<string name="theme_light">خاموش</string>
|
||||
<string name="theme_system">تم پیشفرض سیستم</string>
|
||||
<string name="pref_theme_mode">تم تیره</string>
|
||||
<string name="pref_category_about">درباره</string>
|
||||
<string name="pref_category_advanced">پیشرفته</string>
|
||||
<string name="pref_category_tracking">ردیابی</string>
|
||||
@ -614,4 +613,19 @@
|
||||
<string name="action_ok">باشه</string>
|
||||
<string name="action_sort_next_updated">به روز رسانی مورد انتظار بعدی</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>
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">Viimeisin luku</string>
|
||||
<string name="action_view_chapters">Näytä luvut</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_dark">Päällä</string>
|
||||
<string name="theme_system">Seuraa järjestelmää</string>
|
||||
|
@ -102,7 +102,6 @@
|
||||
<string name="theme_dark">Nakabukas</string>
|
||||
<string name="theme_light">Nakasara</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_advanced">Karagdagan</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">Kabanata %1$s</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="copy">Kopyahin</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_tracking_summary">Isahang pagsabay sa progress, pinahusay na pagsabay</string>
|
||||
<string name="pref_appearance_summary">Tema, ayos ng petsa & 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_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>
|
||||
@ -764,7 +763,7 @@
|
||||
<string name="exclude_scanlators">Ibukod ang mga scanlator</string>
|
||||
<string name="action_create">Lumikha</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="selected">Napili</string>
|
||||
<string name="not_selected">Di napili</string>
|
||||
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">Dernier chapitre</string>
|
||||
<string name="action_view_chapters">Voir les chapitres</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_dark">Activé</string>
|
||||
<string name="theme_system">Par défaut du système</string>
|
||||
|
@ -138,7 +138,6 @@
|
||||
<string name="theme_dark">Activado</string>
|
||||
<string name="theme_light">Desactivado</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_about">Acerca de</string>
|
||||
<string name="pref_category_advanced">Avanzado</string>
|
||||
|
@ -141,7 +141,6 @@
|
||||
<string name="pref_date_format">תבנית תאריך</string>
|
||||
<string name="theme_dark">פעיל</string>
|
||||
<string name="theme_light">כבוי</string>
|
||||
<string name="pref_theme_mode">מצב חשוך</string>
|
||||
<string name="pref_category_about">אודות</string>
|
||||
<string name="pref_category_advanced">מתקדם</string>
|
||||
<string name="pref_category_downloads">הורדות</string>
|
||||
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">नवीनतम अध्याय</string>
|
||||
<string name="action_view_chapters">अध्याय देखें</string>
|
||||
<string name="action_cancel_all">सब रद्द करो</string>
|
||||
<string name="pref_theme_mode">डार्क मोड</string>
|
||||
<string name="theme_light">बंद</string>
|
||||
<string name="theme_dark">चालू करे</string>
|
||||
<string name="theme_system">सिस्टम का पालन करें</string>
|
||||
|
@ -50,7 +50,6 @@
|
||||
<string name="theme_system">Slijedi sustav</string>
|
||||
<string name="theme_dark">Uključ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_advanced">Napredno</string>
|
||||
<string name="pref_category_tracking">Praćenje</string>
|
||||
|
@ -176,7 +176,6 @@
|
||||
<string name="pref_category_security">Biztonság</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="pref_theme_mode">Sötét mód</string>
|
||||
<string name="pref_category_theme">Téma</string>
|
||||
<string name="action_move_to_bottom">Ugrás legalulra</string>
|
||||
<string name="action_move_to_top">Ugrás legfelülre</string>
|
||||
|
@ -293,7 +293,6 @@
|
||||
<string name="action_sort_latest_chapter">Bab terbaru</string>
|
||||
<string name="action_view_chapters">Lihat bab</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_dark">Nyala</string>
|
||||
<string name="theme_system">Ikuti sistem</string>
|
||||
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">Ultimo capitolo</string>
|
||||
<string name="action_view_chapters">Visualizza capitoli</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_dark">Attivo</string>
|
||||
<string name="theme_system">Usa il tema di sistema</string>
|
||||
|
@ -296,7 +296,6 @@
|
||||
<string name="action_sort_latest_chapter">最新章の更新順</string>
|
||||
<string name="action_view_chapters">章を見る</string>
|
||||
<string name="action_cancel_all">すべてキャンセル</string>
|
||||
<string name="pref_theme_mode">ダークモード</string>
|
||||
<string name="theme_light">オフ</string>
|
||||
<string name="theme_dark">オン</string>
|
||||
<string name="theme_system">システムに従う</string>
|
||||
@ -726,8 +725,8 @@
|
||||
<string name="track_delete_text">ローカルの追跡が削除されます。</string>
|
||||
<string name="track_delete_remote_text">%s からも削除</string>
|
||||
<string name="delete_downloaded">ダウンロードを削除</string>
|
||||
<string name="action_filter_interval_late">10+チェック後半</string>
|
||||
<string name="action_filter_interval_dropped">落とした? 20歳以上後半と2ヶ月</string>
|
||||
<string name="action_filter_interval_late">レイト10+チェック</string>
|
||||
<string name="action_filter_interval_dropped">落選? 20歳後半と2ヶ月</string>
|
||||
<string name="action_filter_interval_passed">チェック期間を過ぎました</string>
|
||||
<string name="action_ok">OK</string>
|
||||
<string name="syncing_library">ライブラリを同期しています</string>
|
||||
|
@ -42,7 +42,6 @@
|
||||
<string name="pref_date_format">Format tanggal</string>
|
||||
<string name="theme_dark">Murup</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_downloads">Donlot</string>
|
||||
<string name="action_webview_back">Bali</string>
|
||||
|
@ -79,7 +79,6 @@
|
||||
<string name="pref_category_tracking">თვალყურის დევნება</string>
|
||||
<string name="pref_category_advanced">დამატებით</string>
|
||||
<string name="pref_category_about">ინფორმაცია</string>
|
||||
<string name="pref_theme_mode">ბნელი რეჟიმი</string>
|
||||
<string name="theme_system">სისტემური</string>
|
||||
<string name="theme_light">მსუბუქი</string>
|
||||
<string name="theme_dark">ჩართული</string>
|
||||
|
@ -57,7 +57,6 @@
|
||||
<string name="pref_category_downloads">Жүктеулер</string>
|
||||
<string name="pref_category_advanced">Толығырақ</string>
|
||||
<string name="pref_category_theme">Кейіп</string>
|
||||
<string name="pref_theme_mode">Қараңғы режим</string>
|
||||
<string name="theme_system">Жүйе бойынша</string>
|
||||
<string name="theme_light">Өшірулі</string>
|
||||
<string name="theme_dark">Қосулы</string>
|
||||
|
@ -120,7 +120,6 @@
|
||||
<string name="pref_category_advanced">ការកំណត់ពិសេស</string>
|
||||
<string name="pref_category_about">អំពីរ</string>
|
||||
<string name="pref_category_theme">ពណ៌</string>
|
||||
<string name="pref_theme_mode">ខ្មៅ</string>
|
||||
<string name="theme_system">តាមទូរសព្ទ</string>
|
||||
<string name="theme_light">បិទ</string>
|
||||
<string name="theme_dark">បើក</string>
|
||||
|
@ -17,7 +17,6 @@
|
||||
<string name="theme_system">ಸಿಸ್ಟಮ್ ಅನುಕರಿಸಿ</string>
|
||||
<string name="theme_dark">ಆನ್</string>
|
||||
<string name="theme_light">ಆಫ</string>
|
||||
<string name="pref_theme_mode">ಡಾರ್ಕ್ ಮೋಡ್</string>
|
||||
<string name="pref_category_about">ಅಪ್ಲಿಕೇಶನ್ ಬಗ್ಗೆ</string>
|
||||
<string name="pref_category_advanced">ಸುಧಾರಿತ</string>
|
||||
<string name="pref_category_tracking">ಟ್ರ್ಯಾಕಿಂಗ್</string>
|
||||
|
@ -342,7 +342,6 @@
|
||||
<string name="theme_dark">켜기</string>
|
||||
<string name="theme_light">끄기</string>
|
||||
<string name="theme_system">시스템 설정 사용</string>
|
||||
<string name="pref_theme_mode">다크 모드</string>
|
||||
<string name="action_disable_all">모두 비활성화</string>
|
||||
<string name="spen_next_page">다음 페이지</string>
|
||||
<string name="confirm_lock_change">변경 확인을 위해 인증이 필요합니다</string>
|
||||
|
@ -73,7 +73,6 @@
|
||||
<string name="theme_dark">Įjungta</string>
|
||||
<string name="theme_light">Išjungta</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_about">Apie</string>
|
||||
<string name="pref_category_advanced">Papildomi</string>
|
||||
|
@ -139,7 +139,6 @@
|
||||
<string name="theme_dark">Ieslēgts</string>
|
||||
<string name="theme_light">Izslēgts</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_library">Bibliotēka</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_tracker_score">Izsekošanas rezultāts</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>
|
@ -97,7 +97,6 @@
|
||||
<string name="theme_system">तंत्राचे अनुसरण करा</string>
|
||||
<string name="theme_dark">डार्क</string>
|
||||
<string name="theme_light">लाइट</string>
|
||||
<string name="pref_theme_mode">गडद मोड</string>
|
||||
<string name="pref_category_about">ॲप बद्दल</string>
|
||||
<string name="pref_category_advanced">प्रगत</string>
|
||||
<string name="pref_category_tracking">ट्रॅकिंग</string>
|
||||
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">Bab terkini</string>
|
||||
<string name="action_view_chapters">Buka bab</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_dark">Hidup</string>
|
||||
<string name="theme_system">Ikut sistem</string>
|
||||
|
@ -300,7 +300,6 @@
|
||||
<string name="label_more">Mer</string>
|
||||
<string name="action_view_chapters">Vis kapitler</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_dark">På</string>
|
||||
<string name="theme_system">System</string>
|
||||
|
@ -2,7 +2,6 @@
|
||||
<resources>
|
||||
<string name="theme_dark">अन</string>
|
||||
<string name="theme_light">अफ</string>
|
||||
<string name="pref_theme_mode">अँध्यारो मोड</string>
|
||||
<string name="pref_category_theme">थीम</string>
|
||||
<string name="pref_category_about">बारेमा</string>
|
||||
<string name="pref_category_advanced">उन्नत सेटिङहरू</string>
|
||||
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">Laatste hoofdstuk</string>
|
||||
<string name="action_view_chapters">Hoofdstukken bekijken</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_dark">Aan</string>
|
||||
<string name="theme_system">Volg systeeminstelling</string>
|
||||
|
@ -316,7 +316,6 @@
|
||||
<string name="theme_system">Systemowy</string>
|
||||
<string name="theme_dark">Włą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_top">Przenieś na górę</string>
|
||||
<string name="action_oldest">Najstarsze</string>
|
||||
|
@ -301,10 +301,9 @@
|
||||
<string name="action_sort_latest_chapter">Último capítulo</string>
|
||||
<string name="action_view_chapters">Visualizar os capítulos</string>
|
||||
<string name="action_cancel_all">Cancelar todos</string>
|
||||
<string name="pref_theme_mode">Modo noturno</string>
|
||||
<string name="theme_light">Desligado</string>
|
||||
<string name="theme_dark">Ligado</string>
|
||||
<string name="theme_system">Seguir o sistema</string>
|
||||
<string name="theme_light">Claro</string>
|
||||
<string name="theme_dark">Escuro</string>
|
||||
<string name="theme_system">Sistema</string>
|
||||
<string name="pref_manage_notifications">Gerenciar notificações</string>
|
||||
<string name="pref_category_security">Segurança e privacidade</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="action_menu_overflow_description">Mais opções</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>
|
@ -301,7 +301,6 @@
|
||||
<string name="action_sort_latest_chapter">Último capítulo</string>
|
||||
<string name="action_view_chapters">Ver capítulos</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_dark">Ligado</string>
|
||||
<string name="theme_system">Seguir o do sistema</string>
|
||||
|
@ -65,4 +65,19 @@
|
||||
<item quantity="few">Următoarele %d capitole necitite</item>
|
||||
<item quantity="other">Următoarele %d capitole necitite</item>
|
||||
</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>
|
@ -173,7 +173,7 @@
|
||||
<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_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="version">Versiune</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="action_view_chapters">Vezi capitolele</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_dark">Pornită</string>
|
||||
<string name="theme_system">Tema sistemului</string>
|
||||
@ -331,7 +330,7 @@
|
||||
<string name="action_menu">Meniu</string>
|
||||
<string name="action_newest">Cele mai recente</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="channel_ext_updates">Actualizări de extensie</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="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="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="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>
|
||||
@ -544,7 +543,7 @@
|
||||
<string name="wish_list">Lista de dorințe</string>
|
||||
<string name="complete_list">Lista completă</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_update_only_started">Care nu au fost începute</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="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_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="automatic_background">Automat</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_filter_interval_long">Fetch lunar (28 de zile)</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>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user