Merge remote-tracking branch 'upstream/master'
Some checks failed
Forgejo Release Builder / release (push) Failing after 27m23s
Some checks failed
Forgejo Release Builder / release (push) Failing after 27m23s
This commit is contained in:
commit
af860b5f9d
38 changed files with 454 additions and 261 deletions
5
.github/workflows/codeberg_mirror.yml
vendored
5
.github/workflows/codeberg_mirror.yml
vendored
|
|
@ -16,6 +16,11 @@ jobs:
|
|||
- uses: actions/checkout@v4 # Forgejo-compatible (node20)
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.CODEBERG_SSH }}" >> ~/.ssh/id_rsa
|
||||
chmod 600 ~/.ssh/id_rsa
|
||||
- uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1
|
||||
with:
|
||||
target_repo_url: "git@codeberg.org:cuong-tran/komikku.git"
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ class SyncChaptersWithSource(
|
|||
bookmark = chapter.chapterNumber in deletedBookmarkedChapterNumbers,
|
||||
)
|
||||
|
||||
// Try to to use the fetch date of the original entry to not pollute 'Updates' tab
|
||||
// Try to use the fetch date of the original entry to not pollute 'Updates' tab
|
||||
deletedChapterNumberDateFetchMap[chapter.chapterNumber]?.let {
|
||||
chapter = chapter.copy(dateFetch = it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,6 @@ fun Screen.BulkFavoriteDialogs(
|
|||
AddDuplicateMangaDialog(
|
||||
dialog = dialog,
|
||||
navigator = navigator,
|
||||
state = bulkFavoriteState,
|
||||
onDismiss = bulkFavoriteScreenModel::dismissDialog,
|
||||
stopRunning = bulkFavoriteScreenModel::stopRunning,
|
||||
toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode,
|
||||
|
|
@ -134,10 +133,9 @@ private fun Screen.ShowMigrateDialog(
|
|||
private fun AddDuplicateMangaDialog(
|
||||
dialog: Dialog.AddDuplicateManga,
|
||||
navigator: Navigator?,
|
||||
state: BulkFavoriteScreenModel.State,
|
||||
onDismiss: () -> Unit,
|
||||
stopRunning: () -> Unit,
|
||||
toggleSelectionMode: () -> Unit,
|
||||
toggleSelectionMode: (Boolean) -> Unit,
|
||||
addFavorite: (Manga) -> Unit,
|
||||
showMigrateDialog: (manga: Manga, duplicate: Manga) -> Unit,
|
||||
) {
|
||||
|
|
@ -147,9 +145,7 @@ private fun AddDuplicateMangaDialog(
|
|||
duplicates = dialog.duplicates,
|
||||
onDismissRequest = onDismiss,
|
||||
onConfirm = {
|
||||
if (state.selectionMode) {
|
||||
toggleSelectionMode()
|
||||
}
|
||||
toggleSelectionMode(false)
|
||||
addFavorite(dialog.manga)
|
||||
},
|
||||
onOpenManga = { navigator?.push(MangaScreen(it.id)) },
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import androidx.compose.material3.Surface
|
|||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
|
|
@ -39,6 +38,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
|||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.components.AppBarActions
|
||||
import eu.kanade.presentation.libraryUpdateError.components.LibraryUpdateErrorUiModel
|
||||
import eu.kanade.presentation.libraryUpdateError.components.libraryUpdateErrorUiItems
|
||||
import eu.kanade.presentation.manga.components.Button
|
||||
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem
|
||||
|
|
@ -75,31 +75,33 @@ fun LibraryUpdateErrorScreen(
|
|||
val scope = rememberCoroutineScope()
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
val headerIndexes = remember { mutableStateOf<List<Int>>(emptyList()) }
|
||||
LaunchedEffect(state) {
|
||||
headerIndexes.value = state.getHeaderIndexes()
|
||||
val uiModels = remember(state) { state.getUiModel() }
|
||||
val headerIndexes = remember(state) {
|
||||
uiModels.withIndex()
|
||||
.filter { it.value is LibraryUpdateErrorUiModel.Header }
|
||||
.map { it.index }
|
||||
}
|
||||
|
||||
val enableScrollToTop by remember {
|
||||
val enableScrollToTop by remember(listState) {
|
||||
derivedStateOf {
|
||||
listState.firstVisibleItemIndex > 0
|
||||
}
|
||||
}
|
||||
|
||||
val enableScrollToBottom by remember {
|
||||
val enableScrollToBottom by remember(listState) {
|
||||
derivedStateOf {
|
||||
listState.canScrollForward
|
||||
}
|
||||
}
|
||||
|
||||
val enableScrollToPrevious by remember {
|
||||
val enableScrollToPrevious by remember(headerIndexes, listState) {
|
||||
derivedStateOf {
|
||||
headerIndexes.value.any { it < listState.firstVisibleItemIndex }
|
||||
headerIndexes.any { it < listState.firstVisibleItemIndex }
|
||||
}
|
||||
}
|
||||
val enableScrollToNext by remember {
|
||||
val enableScrollToNext by remember(headerIndexes, listState) {
|
||||
derivedStateOf {
|
||||
headerIndexes.value.any { it > listState.firstVisibleItemIndex }
|
||||
headerIndexes.any { it > listState.firstVisibleItemIndex }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +143,7 @@ fun LibraryUpdateErrorScreen(
|
|||
scrollToPrevious = {
|
||||
scope.launch {
|
||||
listState.scrollToItem(
|
||||
state.getHeaderIndexes()
|
||||
headerIndexes
|
||||
.filter { it < listState.firstVisibleItemIndex }
|
||||
.maxOrNull() ?: 0,
|
||||
)
|
||||
|
|
@ -150,7 +152,7 @@ fun LibraryUpdateErrorScreen(
|
|||
scrollToNext = {
|
||||
scope.launch {
|
||||
listState.scrollToItem(
|
||||
state.getHeaderIndexes()
|
||||
headerIndexes
|
||||
.filter { it > listState.firstVisibleItemIndex }
|
||||
.minOrNull() ?: 0,
|
||||
)
|
||||
|
|
@ -173,7 +175,7 @@ fun LibraryUpdateErrorScreen(
|
|||
state = listState,
|
||||
) {
|
||||
libraryUpdateErrorUiItems(
|
||||
uiModels = state.getUiModel(),
|
||||
uiModels = uiModels,
|
||||
selectionMode = state.selectionMode,
|
||||
onErrorSelected = onErrorSelected,
|
||||
onClick = onClick,
|
||||
|
|
|
|||
|
|
@ -738,7 +738,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||
subtitle = stringResource(SYMR.strings.data_saver_image_quality_summary),
|
||||
enabled = dataSaver != DataSaver.NONE,
|
||||
),
|
||||
kotlin.run {
|
||||
run {
|
||||
val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg()
|
||||
.collectAsState()
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
|
|
@ -808,7 +808,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||
title = stringResource(SYMR.strings.log_level),
|
||||
subtitle = stringResource(SYMR.strings.log_level_summary),
|
||||
),
|
||||
kotlin.run {
|
||||
run {
|
||||
var enableEncryptDatabase by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
if (enableEncryptDatabase) {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||
enabled = sourcePreferences.relatedMangas().get(),
|
||||
),
|
||||
// KMK <--
|
||||
kotlin.run {
|
||||
run {
|
||||
val count by sourcePreferences.sourcesTabCategories().collectAsState()
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.action_edit_categories),
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import com.journeyapps.barcodescanner.ScanContract
|
|||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import eu.kanade.domain.sync.SyncPreferences
|
||||
import eu.kanade.presentation.more.settings.Preference
|
||||
import eu.kanade.presentation.more.settings.screen.SettingsSecurityScreen.PasswordDialog
|
||||
import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
|
||||
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
|
||||
import eu.kanade.presentation.more.settings.screen.data.StorageInfo
|
||||
|
|
@ -562,7 +563,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||
SyncManager.SyncService.SYNCYOMI.value to stringResource(SYMR.strings.syncyomi),
|
||||
SyncManager.SyncService.GOOGLE_DRIVE.value to stringResource(SYMR.strings.google_drive),
|
||||
// KMK -->
|
||||
SyncManager.SyncService.WebDAV.value to stringResource(KMR.strings.web_dav),
|
||||
SyncManager.SyncService.WEB_DAV.value to stringResource(KMR.strings.web_dav),
|
||||
// KMK <--
|
||||
),
|
||||
title = stringResource(SYMR.strings.pref_sync_service),
|
||||
|
|
@ -606,7 +607,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||
SyncManager.SyncService.SYNCYOMI -> getSelfHostPreferences(syncPreferences)
|
||||
SyncManager.SyncService.GOOGLE_DRIVE -> getGoogleDrivePreferences()
|
||||
// KMK -->
|
||||
SyncManager.SyncService.WebDAV -> getWebDavPreferences(syncPreferences)
|
||||
SyncManager.SyncService.WEB_DAV -> getWebDavPreferences(syncPreferences)
|
||||
// KMK <--
|
||||
}
|
||||
|
||||
|
|
@ -759,7 +760,9 @@ object SettingsDataScreen : SearchableSettings {
|
|||
title = stringResource(SYMR.strings.pref_sync_api_key),
|
||||
subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ),
|
||||
onConfirm = {
|
||||
syncPreferences.clientAPIKey().set(it)
|
||||
scope.launch {
|
||||
syncPreferences.clientAPIKey().set(it)
|
||||
}
|
||||
true
|
||||
},
|
||||
icon = null,
|
||||
|
|
@ -808,17 +811,28 @@ object SettingsDataScreen : SearchableSettings {
|
|||
true
|
||||
},
|
||||
),
|
||||
Preference.PreferenceItem.EditTextPreference(
|
||||
preference = syncPreferences.webDavPassword(),
|
||||
title = stringResource(KMR.strings.pref_webdav_password),
|
||||
subtitle = stringResource(KMR.strings.pref_webdav_password_summ),
|
||||
onValueChanged = { newValue ->
|
||||
scope.launch {
|
||||
syncPreferences.webDavPassword().set(newValue)
|
||||
}
|
||||
true
|
||||
},
|
||||
),
|
||||
run {
|
||||
var dialogOpen by remember { mutableStateOf(false) }
|
||||
if (dialogOpen) {
|
||||
PasswordDialog(
|
||||
onDismissRequest = { dialogOpen = false },
|
||||
onReturnPassword = { password ->
|
||||
dialogOpen = false
|
||||
scope.launch {
|
||||
syncPreferences.webDavPassword().set(password.replace("\n", ""))
|
||||
}
|
||||
},
|
||||
title = KMR.strings.pref_webdav_password,
|
||||
)
|
||||
}
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(KMR.strings.pref_webdav_password),
|
||||
subtitle = stringResource(KMR.strings.pref_webdav_password_summ),
|
||||
onClick = {
|
||||
dialogOpen = true
|
||||
},
|
||||
)
|
||||
},
|
||||
Preference.PreferenceItem.EditTextPreference(
|
||||
preference = syncPreferences.webDavFolder(),
|
||||
title = stringResource(KMR.strings.pref_webdav_folder),
|
||||
|
|
@ -832,7 +846,7 @@ object SettingsDataScreen : SearchableSettings {
|
|||
),
|
||||
)
|
||||
}
|
||||
// MK <--
|
||||
// KMK <--
|
||||
|
||||
@Composable
|
||||
private fun getSyncNowPref(): Preference.PreferenceGroup {
|
||||
|
|
|
|||
|
|
@ -293,9 +293,14 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||
title = stringResource(KMR.strings.pref_show_empty_categories_search),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = libraryPreferences.syncOnAdd(),
|
||||
title = stringResource(KMR.strings.pref_sync_manga_on_add),
|
||||
subtitle = stringResource(KMR.strings.pref_sync_manga_on_add_description),
|
||||
preference = libraryPreferences.fetchMetadataOnAdd(),
|
||||
title = stringResource(KMR.strings.pref_fetch_manga_metadata_on_add),
|
||||
subtitle = stringResource(KMR.strings.pref_fetch_manga_metadata_on_add_description),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
preference = libraryPreferences.fetchChaptersOnAdd(),
|
||||
title = stringResource(KMR.strings.pref_fetch_manga_chapters_on_add),
|
||||
subtitle = stringResource(KMR.strings.pref_fetch_manga_chapters_on_add_description),
|
||||
),
|
||||
// KMK <--
|
||||
),
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
enabled = passwordProtectDownloads,
|
||||
|
||||
),
|
||||
kotlin.run {
|
||||
run {
|
||||
var dialogOpen by remember { mutableStateOf(false) }
|
||||
if (dialogOpen) {
|
||||
PasswordDialog(
|
||||
|
|
@ -183,7 +183,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
securityPreferences.cbzPassword().set("")
|
||||
},
|
||||
),
|
||||
kotlin.run {
|
||||
run {
|
||||
val navigator = LocalNavigator.currentOrThrow
|
||||
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
|
|
@ -199,7 +199,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
},
|
||||
)
|
||||
},
|
||||
kotlin.run {
|
||||
run {
|
||||
val selection by securityPreferences.authenticatorDays().collectAsState()
|
||||
var dialogOpen by remember { mutableStateOf(false) }
|
||||
if (dialogOpen) {
|
||||
|
|
@ -332,13 +332,16 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||
fun PasswordDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onReturnPassword: (String) -> Unit,
|
||||
// KMK -->
|
||||
title: StringResource = SYMR.strings.cbz_archive_password,
|
||||
// KMK <--
|
||||
) {
|
||||
var password by rememberSaveable { mutableStateOf("") }
|
||||
var passwordVisibility by remember { mutableStateOf(false) }
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
||||
title = { Text(text = stringResource(SYMR.strings.cbz_archive_password)) },
|
||||
title = { Text(text = stringResource(title)) },
|
||||
text = {
|
||||
TextField(
|
||||
value = password,
|
||||
|
|
|
|||
|
|
@ -112,6 +112,9 @@ fun UpdateScreen(
|
|||
else -> {
|
||||
val scope = rememberCoroutineScope()
|
||||
var isRefreshing by remember { mutableStateOf(false) }
|
||||
// KMK -->
|
||||
val uiModels = remember(state.items) { state.getUiModel() }
|
||||
// KMK <--
|
||||
|
||||
PullRefresh(
|
||||
refreshing = isRefreshing,
|
||||
|
|
@ -134,8 +137,8 @@ fun UpdateScreen(
|
|||
updatesLastUpdatedItem(lastUpdated)
|
||||
|
||||
updatesUiItems(
|
||||
uiModels = state.getUiModel(),
|
||||
// KMK -->
|
||||
uiModels = uiModels,
|
||||
expandedState = state.expandedState,
|
||||
collapseToggle = collapseToggle,
|
||||
usePanoramaCover = usePanoramaCover,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class SyncManager(
|
|||
SYNCYOMI(1),
|
||||
GOOGLE_DRIVE(2),
|
||||
// KMK -->
|
||||
WebDAV(3),
|
||||
WEB_DAV(3),
|
||||
// KMK <--
|
||||
;
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ class SyncManager(
|
|||
}
|
||||
|
||||
// KMK -->
|
||||
SyncService.WebDAV -> {
|
||||
SyncService.WEB_DAV -> {
|
||||
WebDavSyncService(context, json, syncPreferences, notifier)
|
||||
}
|
||||
// KMK <--
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class WebDavSyncService(
|
|||
private val url: String = syncPreferences.webDavUrl().get().trim()
|
||||
private val folder: String = syncPreferences.webDavFolder().get().trim('/')
|
||||
private val username: String = syncPreferences.webDavUsername().get().trim()
|
||||
private val password: String = syncPreferences.webDavPassword().get().trim()
|
||||
private val password: String = syncPreferences.webDavPassword().get()
|
||||
private val credentials: String = Credentials.basic(username, password)
|
||||
|
||||
private fun buildWebDavFileUrl(fileName: String = "backup.proto"): String {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
|||
import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||
import eu.kanade.tachiyomi.source.online.NamespaceSource
|
||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import exh.metadata.metadata.NHentaiSearchMetadata
|
||||
import exh.metadata.metadata.RaisedSearchMetadata
|
||||
import exh.metadata.metadata.base.RaisedTag
|
||||
|
|
@ -30,7 +29,7 @@ import kotlinx.serialization.Serializable
|
|||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
|
||||
class NHentai(delegate: HttpSource, val context: Context) :
|
||||
DelegatedHttpSource(delegate),
|
||||
|
|
@ -72,12 +71,8 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
}
|
||||
|
||||
override suspend fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) {
|
||||
val body = input.body.string()
|
||||
val server = MEDIA_SERVER_REGEX.find(body)?.groupValues?.get(1)?.toInt() ?: 1
|
||||
val json = GALLERY_JSON_REGEX.find(body)!!.groupValues[1].replace(
|
||||
UNICODE_ESCAPE_REGEX,
|
||||
) { it.groupValues[1].toInt(radix = 16).toChar().toString() }
|
||||
val jsonResponse = jsonParser.decodeFromString<JsonResponse>(json)
|
||||
if (nhConfig == null) getNhConfig()
|
||||
val jsonResponse = jsonParser.decodeFromString<JsonResponse>(input.body.string())
|
||||
|
||||
with(metadata) {
|
||||
nhId = jsonResponse.id
|
||||
|
|
@ -88,8 +83,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
|
||||
mediaId = jsonResponse.mediaId
|
||||
|
||||
mediaServer = server
|
||||
|
||||
jsonResponse.title?.let { title ->
|
||||
japaneseTitle = title.japanese
|
||||
shortTitle = title.pretty
|
||||
|
|
@ -98,16 +91,11 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
|
||||
preferredTitle = this@NHentai.preferredTitle
|
||||
|
||||
jsonResponse.images?.let { images ->
|
||||
coverImageType = input.asJsoup(body).parseCoverType()
|
||||
?: images.cover?.type
|
||||
images.pages.mapNotNull {
|
||||
it.type
|
||||
}.let {
|
||||
pageImageTypes = it
|
||||
}
|
||||
thumbnailImageType = images.thumbnail?.type
|
||||
}
|
||||
coverImageUrl =
|
||||
jsonResponse.cover?.path?.let { "$thumbServer/$it" }
|
||||
?: jsonResponse.thumbnail?.path?.let { "$thumbServer/$it" }
|
||||
|
||||
pageImagePreviewUrls = jsonResponse.pages.mapNotNull { it.thumbnail }
|
||||
|
||||
scanlator = jsonResponse.scanlator?.trimOrNull()
|
||||
|
||||
|
|
@ -128,17 +116,13 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
}
|
||||
}
|
||||
|
||||
// KMK -->
|
||||
/**
|
||||
* Site JSON is saying cover of type `w` but instead it's using cover like `cover.jpg.webp`
|
||||
*/
|
||||
private fun Document.parseCoverType(): String? {
|
||||
return selectFirst("#cover > a > img")?.attr("data-src")
|
||||
?.substringAfterLast('/')
|
||||
?.substringAfter('.')
|
||||
?.first()?.toString()
|
||||
}
|
||||
// KMK <--
|
||||
@Serializable
|
||||
data class JsonConfig(
|
||||
@SerialName("image_servers")
|
||||
val imageServers: List<String> = emptyList(),
|
||||
@SerialName("thumb_servers")
|
||||
val thumbServers: List<String> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class JsonResponse(
|
||||
|
|
@ -146,7 +130,8 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
@SerialName("media_id")
|
||||
val mediaId: String? = null,
|
||||
val title: JsonTitle? = null,
|
||||
val images: JsonImages? = null,
|
||||
val cover: JsonPage? = null,
|
||||
val thumbnail: JsonPage? = null,
|
||||
val scanlator: String? = null,
|
||||
@SerialName("upload_date")
|
||||
val uploadDate: Long? = null,
|
||||
|
|
@ -155,6 +140,7 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
val numPages: Int? = null,
|
||||
@SerialName("num_favorites")
|
||||
val numFavorites: Long? = null,
|
||||
val pages: List<JsonPage> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
@ -164,21 +150,12 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
val pretty: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class JsonImages(
|
||||
val pages: List<JsonPage> = emptyList(),
|
||||
val cover: JsonPage? = null,
|
||||
val thumbnail: JsonPage? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class JsonPage(
|
||||
@SerialName("t")
|
||||
val type: String? = null,
|
||||
@SerialName("w")
|
||||
val path: String? = null,
|
||||
val width: Long? = null,
|
||||
@SerialName("h")
|
||||
val height: Long? = null,
|
||||
val thumbnail: String? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
|
@ -203,20 +180,16 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
}
|
||||
|
||||
override suspend fun getPagePreviewList(manga: SManga, chapters: List<SChapter>, page: Int): PagePreviewPage {
|
||||
if (nhConfig == null) getNhConfig()
|
||||
val metadata = fetchOrLoadMetadata(manga.id()) {
|
||||
client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
|
||||
}
|
||||
return PagePreviewPage(
|
||||
page,
|
||||
metadata.pageImageTypes.mapIndexed { index, s ->
|
||||
metadata.pageImagePreviewUrls.mapIndexed { index, path ->
|
||||
PagePreviewInfo(
|
||||
index + 1,
|
||||
imageUrl = thumbnailUrlFromType(
|
||||
metadata.mediaId!!,
|
||||
metadata.mediaServer ?: 1,
|
||||
index + 1,
|
||||
s,
|
||||
)!!,
|
||||
imageUrl = "$thumbServer/$path",
|
||||
)
|
||||
},
|
||||
false,
|
||||
|
|
@ -224,15 +197,23 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
)
|
||||
}
|
||||
|
||||
private fun thumbnailUrlFromType(
|
||||
mediaId: String,
|
||||
mediaServer: Int,
|
||||
page: Int,
|
||||
t: String,
|
||||
) = NHentaiSearchMetadata.typeToExtension(t)?.let {
|
||||
"https://t$mediaServer.nhentai.net/galleries/$mediaId/${page}t.$it"
|
||||
var nhConfig: JsonConfig? = null
|
||||
suspend fun getNhConfig() {
|
||||
try {
|
||||
val body = withIOContext { client.newCall(GET("https://nhentai.net/api/v2/config", headers)).awaitSuccess() }
|
||||
.use { it.body.string() }
|
||||
nhConfig = jsonParser.decodeFromString<JsonConfig>(body)
|
||||
} catch (_: Exception) {
|
||||
nhConfig = JsonConfig(
|
||||
(1..4).map { n -> "https://i$n.nhentai.net" },
|
||||
(1..4).map { n -> "https://t$n.nhentai.net" },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val thumbServer
|
||||
get() = nhConfig?.thumbServers?.randomOrNull() ?: "https://t1.nhentai.net"
|
||||
|
||||
override suspend fun fetchPreviewImage(page: PagePreviewInfo, cacheControl: CacheControl?): Response {
|
||||
return client.newCachelessCallWithProgress(
|
||||
if (cacheControl != null) {
|
||||
|
|
@ -248,10 +229,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
|||
private val jsonParser = Json {
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
|
||||
private val GALLERY_JSON_REGEX = Regex(".parse\\(\"(.*)\"\\);")
|
||||
private val MEDIA_SERVER_REGEX = Regex("media_server\\s*:\\s*(\\d+)")
|
||||
private val UNICODE_ESCAPE_REGEX = Regex("\\\\u([0-9a-fA-F]{4})")
|
||||
private const val TITLE_PREF = "Display manga title as:"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package eu.kanade.tachiyomi.ui.browse
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
|
|
@ -60,22 +59,20 @@ class BulkFavoriteScreenModel(
|
|||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||
private val addTracks: AddTracks = Injekt.get(),
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||
) : StateScreenModel<BulkFavoriteScreenModel.State>(initialState) {
|
||||
|
||||
fun backHandler() {
|
||||
toggleSelectionMode()
|
||||
toggleSelectionMode(false)
|
||||
}
|
||||
|
||||
fun toggleSelectionMode(newMode: Boolean? = null) {
|
||||
if (state.value.selectionMode) {
|
||||
clearSelection()
|
||||
mutableState.update { state ->
|
||||
val mode = newMode ?: !state.selectionMode
|
||||
state.copy(
|
||||
selectionMode = mode,
|
||||
selection = if (mode) state.selection else persistentListOf(),
|
||||
)
|
||||
}
|
||||
mutableState.update { it.copy(selectionMode = newMode ?: !it.selectionMode) }
|
||||
}
|
||||
|
||||
private fun clearSelection() {
|
||||
mutableState.update { it.copy(selection = persistentListOf()) }
|
||||
}
|
||||
|
||||
fun select(manga: Manga) {
|
||||
|
|
@ -83,7 +80,7 @@ class BulkFavoriteScreenModel(
|
|||
}
|
||||
|
||||
/**
|
||||
* @param toSelectedState set to true to only Select, set to false to only Unselect
|
||||
* @param toSelectedState set to `true` to only Select, set to `false` to only Unselect
|
||||
*/
|
||||
fun toggleSelection(manga: Manga, toSelectedState: Boolean? = null) {
|
||||
mutableState.update { state ->
|
||||
|
|
@ -131,7 +128,7 @@ class BulkFavoriteScreenModel(
|
|||
if (entryWithDuplicates != null) {
|
||||
val (index, manga, duplicates) = entryWithDuplicates
|
||||
if (state.value.selection.size == 1) {
|
||||
// If only one manga is selected, show the multiple duplicates dialog.
|
||||
// If only one manga is selected, show the multiple-duplicates dialog.
|
||||
setDialog(Dialog.AddDuplicateManga(manga, duplicates))
|
||||
} else {
|
||||
setDialog(Dialog.BulkAllowDuplicate(manga, duplicates, index))
|
||||
|
|
@ -154,7 +151,7 @@ class BulkFavoriteScreenModel(
|
|||
val mangaList = if (skipAllDuplicates) getNotDuplicateLibraryMangas() else state.value.selection
|
||||
if (mangaList.isEmpty()) {
|
||||
stopRunning()
|
||||
toggleSelectionMode()
|
||||
toggleSelectionMode(false)
|
||||
return@launch
|
||||
}
|
||||
val categories = getCategories()
|
||||
|
|
@ -243,7 +240,7 @@ class BulkFavoriteScreenModel(
|
|||
}
|
||||
stopRunning()
|
||||
}
|
||||
toggleSelectionMode(newMode = false)
|
||||
toggleSelectionMode(false)
|
||||
}
|
||||
|
||||
private fun moveMangaToCategoriesAndAddToLibrary(manga: Manga, categories: List<Long>) {
|
||||
|
|
@ -256,17 +253,22 @@ class BulkFavoriteScreenModel(
|
|||
setMangaDefaultChapterFlags.await(manga)
|
||||
addTracks.bindEnhancedTrackers(manga, source)
|
||||
updateManga.awaitUpdateFavorite(manga.id, true)
|
||||
if (libraryPreferences.syncOnAdd().get()) {
|
||||
val fetchMetadataOnAdd = libraryPreferences.fetchMetadataOnAdd().get()
|
||||
val fetchChaptersOnAdd = libraryPreferences.fetchChaptersOnAdd().get()
|
||||
if (fetchMetadataOnAdd || fetchChaptersOnAdd) {
|
||||
val sManga = manga.toSManga()
|
||||
val remoteManga = source.getMangaDetails(sManga)
|
||||
val chapters = source.getChapterList(sManga)
|
||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
||||
updateManga.awaitUpdateFromSource(manga, remoteManga, false, coverCache)
|
||||
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||
if (fetchMetadataOnAdd) {
|
||||
val remoteMetadata = source.getMangaDetails(sManga)
|
||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
||||
updateManga.awaitUpdateFromSource(manga, remoteMetadata, false, coverCache)
|
||||
}
|
||||
if (fetchChaptersOnAdd) {
|
||||
val chapters = source.getChapterList(sManga)
|
||||
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
snackbarHostState.showSnackbar(message = "Failed to sync manga: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -351,18 +353,23 @@ class BulkFavoriteScreenModel(
|
|||
}
|
||||
|
||||
updateManga.await(new.toMangaUpdate())
|
||||
if (new.favorite && libraryPreferences.syncOnAdd().get()) {
|
||||
val fetchMetadataOnAdd = libraryPreferences.fetchMetadataOnAdd().get()
|
||||
val fetchChaptersOnAdd = libraryPreferences.fetchChaptersOnAdd().get()
|
||||
if (new.favorite && (fetchMetadataOnAdd || fetchChaptersOnAdd)) {
|
||||
withIOContext {
|
||||
try {
|
||||
val sManga = manga.toSManga()
|
||||
val remoteManga = source.getMangaDetails(sManga)
|
||||
val chapters = source.getChapterList(sManga)
|
||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
||||
updateManga.awaitUpdateFromSource(manga, remoteManga, false, coverCache)
|
||||
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||
if (fetchMetadataOnAdd) {
|
||||
val remoteMetadata = source.getMangaDetails(sManga)
|
||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
||||
updateManga.awaitUpdateFromSource(manga, remoteMetadata, false, coverCache)
|
||||
}
|
||||
if (fetchChaptersOnAdd) {
|
||||
val chapters = source.getChapterList(sManga)
|
||||
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
snackbarHostState.showSnackbar(message = "Failed to sync manga: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,11 +149,10 @@ class MigrateMangaScreenModel(
|
|||
selectedMangaIds.addOrRemove(it.manga.id, selected)
|
||||
it.copy(selected = selected)
|
||||
}
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
state.copy(titleList = newItems.toImmutableList())
|
||||
}
|
||||
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
}
|
||||
|
||||
fun invertSelection() {
|
||||
|
|
@ -162,10 +161,10 @@ class MigrateMangaScreenModel(
|
|||
selectedMangaIds.addOrRemove(it.manga.id, !it.selected)
|
||||
it.copy(selected = !it.selected)
|
||||
}
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
state.copy(titleList = newItems.toImmutableList())
|
||||
}
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
}
|
||||
// KMK <--
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -120,7 +119,6 @@ open class BrowseSourceScreenModel(
|
|||
private val toggleIncognito: ToggleIncognito = Injekt.get(),
|
||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||
// KMK <--
|
||||
|
||||
// SY -->
|
||||
|
|
@ -424,20 +422,23 @@ open class BrowseSourceScreenModel(
|
|||
|
||||
updateManga.await(new.toMangaUpdate())
|
||||
// KMK -->
|
||||
if (new.favorite && libraryPreferences.syncOnAdd().get()) {
|
||||
val fetchMetadataOnAdd = libraryPreferences.fetchMetadataOnAdd().get()
|
||||
val fetchChaptersOnAdd = libraryPreferences.fetchChaptersOnAdd().get()
|
||||
if (new.favorite && (fetchMetadataOnAdd || fetchChaptersOnAdd)) {
|
||||
withIOContext {
|
||||
try {
|
||||
val sManga = manga.toSManga()
|
||||
val remoteManga = source.getMangaDetails(sManga)
|
||||
val chapters = source.getChapterList(sManga)
|
||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
||||
updateManga.awaitUpdateFromSource(manga, remoteManga, false, coverCache)
|
||||
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||
if (fetchMetadataOnAdd) {
|
||||
val remoteMetadata = source.getMangaDetails(sManga)
|
||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
||||
updateManga.awaitUpdateFromSource(manga, remoteMetadata, false, coverCache)
|
||||
}
|
||||
if (fetchChaptersOnAdd) {
|
||||
val chapters = source.getChapterList(sManga)
|
||||
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
screenModelScope.launch {
|
||||
snackbarHostState.showSnackbar(message = "Failed to sync manga: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,11 +129,10 @@ class LibraryUpdateErrorScreenModel(
|
|||
selectedErrorIds.addOrRemove(it.error.errorId, selected)
|
||||
it.copy(selected = selected)
|
||||
}
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
state.copy(items = newItems)
|
||||
}
|
||||
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
}
|
||||
|
||||
fun invertSelection() {
|
||||
|
|
@ -142,10 +141,10 @@ class LibraryUpdateErrorScreenModel(
|
|||
selectedErrorIds.addOrRemove(it.error.errorId, !it.selected)
|
||||
it.copy(selected = !it.selected)
|
||||
}
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
state.copy(items = newItems)
|
||||
}
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
|
|
@ -189,11 +188,6 @@ data class LibraryUpdateErrorScreenState(
|
|||
}
|
||||
return uiModels
|
||||
}
|
||||
|
||||
fun getHeaderIndexes(): List<Int> = getUiModel()
|
||||
.withIndex()
|
||||
.filter { it.value is LibraryUpdateErrorUiModel.Header }
|
||||
.map { it.index }
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ import eu.kanade.tachiyomi.util.system.toast
|
|||
import exh.debug.DebugToggles
|
||||
import exh.eh.EHentaiUpdateHelper
|
||||
import exh.log.xLogD
|
||||
import exh.log.xLogE
|
||||
import exh.md.utils.FollowStatus
|
||||
import exh.metadata.metadata.RaisedSearchMetadata
|
||||
import exh.metadata.metadata.base.FlatMetadata
|
||||
|
|
@ -1851,7 +1852,7 @@ class MangaScreenModel(
|
|||
}
|
||||
// KMK: auto track MangaDex
|
||||
mdTrack.id !in tracks.map { it.trackerId } -> {
|
||||
tracks + createMdListTrack()
|
||||
createMdListTrack()?.let { tracks + it } ?: tracks
|
||||
}
|
||||
else -> tracks
|
||||
}
|
||||
|
|
@ -1880,21 +1881,27 @@ class MangaScreenModel(
|
|||
}
|
||||
|
||||
// SY -->
|
||||
private suspend fun createMdListTrack(): Track {
|
||||
val state = successState!!
|
||||
val mdManga = state.manga.takeIf { it.source in mangaDexSourceIds }
|
||||
?: state.mergedData?.manga?.values?.find { it.source in mangaDexSourceIds }
|
||||
?: throw IllegalArgumentException("Could not create initial track")
|
||||
val track = trackerManager.mdList.createInitialTracker(state.manga, mdManga)
|
||||
.toDomainTrack(false)!!
|
||||
insertTrack.await(track)
|
||||
/* KMK -->
|
||||
return TrackItem(
|
||||
getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id },
|
||||
trackerManager.mdList,
|
||||
)
|
||||
KMK <-- */
|
||||
return getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id }
|
||||
private suspend fun createMdListTrack(): Track? {
|
||||
try {
|
||||
val state = successState!!
|
||||
val mdManga = state.manga.takeIf { it.source in mangaDexSourceIds }
|
||||
?: state.mergedData?.manga?.values?.find { it.source in mangaDexSourceIds }
|
||||
?: throw IllegalArgumentException("Entry does not belong to MangaDex")
|
||||
val track = trackerManager.mdList.createInitialTracker(state.manga, mdManga)
|
||||
.toDomainTrack(false)
|
||||
?: throw IllegalStateException("Could not create initial track")
|
||||
insertTrack.await(track)
|
||||
/* KMK -->
|
||||
return TrackItem(
|
||||
getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id },
|
||||
trackerManager.mdList,
|
||||
)
|
||||
KMK <-- */
|
||||
return getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id }
|
||||
} catch (e: Exception) {
|
||||
xLogE("Failed to create MangaDex track", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
|
|
|
|||
|
|
@ -461,11 +461,12 @@ class UpdatesScreenModel(
|
|||
selectedChapterIds.addOrRemove(it.update.chapterId, selected)
|
||||
it.copy(selected = selected)
|
||||
}
|
||||
// KMK -->
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
// KMK <--
|
||||
state.copy(items = newItems.toPersistentList())
|
||||
}
|
||||
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
}
|
||||
|
||||
fun invertSelection() {
|
||||
|
|
@ -474,10 +475,12 @@ class UpdatesScreenModel(
|
|||
selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected)
|
||||
it.copy(selected = !it.selected)
|
||||
}
|
||||
// KMK -->
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
// KMK <--
|
||||
state.copy(items = newItems.toPersistentList())
|
||||
}
|
||||
selectedPositions[0] = -1
|
||||
selectedPositions[1] = -1
|
||||
}
|
||||
|
||||
fun setDialog(dialog: Dialog?) {
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class BrowseRecommendsScreen(
|
|||
scrollBehavior = scrollBehavior,
|
||||
// KMK -->
|
||||
toggleSelectionMode = {
|
||||
bulkFavoriteScreenModel.toggleSelectionMode()
|
||||
bulkFavoriteScreenModel.toggleSelectionMode(true)
|
||||
}.takeIf { !isExternalSource },
|
||||
isRunning = bulkFavoriteState.isRunning,
|
||||
// KMK <--
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@ fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
|||
|
||||
binding.pages.text = context.pluralStringResource(
|
||||
SYMR.plurals.num_pages,
|
||||
meta.pageImageTypes.size,
|
||||
meta.pageImageTypes.size,
|
||||
meta.pageImagePreviewUrls.size,
|
||||
meta.pageImagePreviewUrls.size,
|
||||
)
|
||||
// KMK -->
|
||||
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24, iconColor, 4.dpToPx)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ CREATE TABLE chapters(
|
|||
|
||||
CREATE INDEX chapters_manga_id_index ON chapters(manga_id);
|
||||
CREATE INDEX chapters_unread_by_manga_index ON chapters(manga_id, read) WHERE read = 0;
|
||||
CREATE INDEX idx_chapters_url ON chapters(url);
|
||||
|
||||
CREATE TRIGGER update_last_modified_at_chapters
|
||||
AFTER UPDATE ON chapters
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ CREATE TABLE excluded_scanlators(
|
|||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX excluded_scanlators_manga_id_index ON excluded_scanlators(manga_id);
|
||||
CREATE INDEX idx_excluded_scanlators_manga_id_scanlator ON excluded_scanlators(manga_id, scanlator);
|
||||
|
||||
insert:
|
||||
INSERT INTO excluded_scanlators(manga_id, scanlator)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ CREATE TABLE history(
|
|||
);
|
||||
|
||||
CREATE INDEX history_history_chapter_id_index ON history(chapter_id);
|
||||
CREATE INDEX idx_history_last_read ON history(last_read) WHERE last_read > 0;
|
||||
|
||||
getHistoryByMangaId:
|
||||
SELECT
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ CREATE TABLE mangas(
|
|||
|
||||
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
|
||||
CREATE INDEX mangas_url_index ON mangas(url);
|
||||
CREATE INDEX idx_mangas_source ON mangas(source);
|
||||
|
||||
CREATE TRIGGER update_last_favorited_at_mangas
|
||||
AFTER UPDATE OF favorite ON mangas
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ CREATE TABLE mangas_categories(
|
|||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_mangas_categories_manga_id ON mangas_categories(manga_id);
|
||||
CREATE INDEX idx_mangas_categories_category_id ON mangas_categories(category_id);
|
||||
|
||||
CREATE TRIGGER insert_manga_category_update_version AFTER INSERT ON mangas_categories
|
||||
BEGIN
|
||||
UPDATE mangas
|
||||
|
|
|
|||
9
data/src/main/sqldelight/tachiyomi/migrations/45.sqm
Normal file
9
data/src/main/sqldelight/tachiyomi/migrations/45.sqm
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
-- Migration to add performance indexes
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mangas_source ON mangas(source);
|
||||
CREATE INDEX IF NOT EXISTS idx_chapters_url ON chapters(url);
|
||||
CREATE INDEX IF NOT EXISTS idx_mangas_categories_manga_id ON mangas_categories(manga_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_mangas_categories_category_id ON mangas_categories(category_id);
|
||||
DROP INDEX IF EXISTS excluded_scanlators_manga_id_index;
|
||||
CREATE INDEX IF NOT EXISTS idx_excluded_scanlators_manga_id_scanlator ON excluded_scanlators(manga_id, scanlator);
|
||||
CREATE INDEX IF NOT EXISTS idx_history_last_read ON history(last_read) WHERE last_read > 0;
|
||||
|
|
@ -74,7 +74,8 @@ class LibraryPreferences(
|
|||
fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
|
||||
|
||||
// KMK -->
|
||||
fun syncOnAdd() = preferenceStore.getBoolean("sync_on_add", false)
|
||||
fun fetchMetadataOnAdd() = preferenceStore.getBoolean("fetch_metadata_on_add", false)
|
||||
fun fetchChaptersOnAdd() = preferenceStore.getBoolean("fetch_chapters_on_add", false)
|
||||
// KMK <--
|
||||
|
||||
fun showContinueReadingButton() = preferenceStore.getBoolean(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="action_source_search">البحث عن مصدر</string>
|
||||
<string name="action_source_search">البحث داخل المصدر</string>
|
||||
<string name="action_toggle_nsfw_only">تبديل NSFW فقط</string>
|
||||
<string name="action_bulk_select">وضع التحديد بالجملة</string>
|
||||
<string name="action_faq_and_guides">الأسئلة الشائعة والأدلة</string>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<string name="download_cache_renew_interval_6hour">6 ساعات</string>
|
||||
<string name="download_cache_renew_interval_12hour">12 ساعة</string>
|
||||
<string name="download_cache_renew_interval_24hour">1 يوم</string>
|
||||
<string name="download_cache_renew_interval_info">إذا تم تعيين إلى \"يدوي\"، إما تحديث كل مانغا أو باستخدام \"فهرس التنزيلات غير صحيح\" في \"المتقدم\"</string>
|
||||
<string name="download_cache_renew_interval_info">إذا تم تعيين إلى \"يدوي\"، إما قم بتحديث كل مانجا أو باستخدام \"إعادة الفهرسة للتنزيلات\" في \"المتقدم\"</string>
|
||||
<string name="pref_clean_invalid_downloads">تنظيف التنزيلات غير صالحة</string>
|
||||
<string name="pref_clean_invalid_downloads_summary">العثور على جميع التنزيلات والملفات والمجلدات التي لم يتم حفظها في مكتبتك وإزالتها</string>
|
||||
<string name="invalid_downloads_cleaned">تم تنظيف التنزيلات غير صالحة</string>
|
||||
|
|
@ -120,6 +120,46 @@
|
|||
<string name="action_copy_to_clipboard_second_page">نسخ الصفحة الثانية إلى الحافظة</string>
|
||||
<string name="action_copy_to_clipboard_combined_page">نسخ الصفحة المدمجة إلى الحافظة</string>
|
||||
<string name="auto_update_app_disable_auto_download">عدم التحميل التلقائي للتحديث</string>
|
||||
<string name="action_remove_merged">إزالة المدخلات الأصلية من المكتبة؟</string>
|
||||
<string name="action_remove_merged">إزالة العناصر الأصلية من المكتبة؟</string>
|
||||
<string name="batch_add_description">الدعم: MangaDex, E-H, ExH, nH, 8Muses, Tsumino</string>
|
||||
<string name="action_open_folder">فتح مجلد</string>
|
||||
<string name="action_update">تحديث</string>
|
||||
<string name="action_filter_unfinished_manga">مانجا غير مكتملة</string>
|
||||
<string name="action_filter_unfinished_chapter">فصل غير مكتملة</string>
|
||||
<string name="action_filter_non_library_entries">عناصر خارج المكتبة</string>
|
||||
<string name="pref_viewer_nav_smaller_tap_zone">نطاق نقر أصغر</string>
|
||||
<string name="error_opening_folder">فشل فتح المجلد</string>
|
||||
<string name="pref_pinch_to_zoom">ضم الأصابع للتكبير</string>
|
||||
<string name="pref_paged_disable_zoom_in">إلغاء تفعيل التكبير</string>
|
||||
<string name="pref_landscape_zoom_type">وضع التكبير للصور العريضة</string>
|
||||
<string name="scale_type_double">تكبير مزدوج</string>
|
||||
<string name="pref_webtoon_scale_type">تكبير ذكي للشاشات العريضة</string>
|
||||
<string name="pref_smart_scale_long_strip_gap">تنفيذ التكبير الذكي ل \"شرائح طويلة مع فواصل\"</string>
|
||||
<string name="scale_type_16_9">16:9</string>
|
||||
<string name="scale_type_20_9">20:9</string>
|
||||
<string name="scale_type_4_3">4:3</string>
|
||||
<string name="scale_type_3_2">3:2</string>
|
||||
<string name="pref_top_align_cover">محاذاة الغلاف للأعلى</string>
|
||||
<string name="pref_top_align_cover_summary">إظهار الغلاف محاذي للأعلى بجانب بيانات المانجا</string>
|
||||
<string name="theme_doom">دمار</string>
|
||||
<string name="pref_library_filter_categories_details">لن يتم عرض العناصر في الفئات المستبعدة حتى لو كانت موجودة أيضًا في الفئات المشمولة.</string>
|
||||
<string name="updating">جاري التحديث</string>
|
||||
<string name="pref_show_empty_categories_search">عرض الفئات الفارغة أثناء البحث/التصفية</string>
|
||||
<string name="pref_track_on_add_library">فتح قائمة المزامنة عند الإضافة للمكتبة</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add">تحديث بيانات المانجا عند الإضافة للمكتبة</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add_description">جلب العنوان والوصف والعلامات وغيرها عند إضافة المانجا إلى المكتبة</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add">جلب الفصول عند الإضافة للمكتبة</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add_description">تحديث قائمة الفصول مباشرة بعد إضافة المانجا إلى المكتبة</string>
|
||||
<string name="web_dav">WebDAV</string>
|
||||
<string name="pref_webdav_url">رابط سيرفر WebDAV</string>
|
||||
<string name="pref_webdav_url_summ">ادخل رابط سيرفر WebDAV</string>
|
||||
<string name="pref_webdav_username">اسم المستخدم</string>
|
||||
<string name="pref_webdav_username_summ">ادخل اسم مستخدم WebDAV الخاص بك</string>
|
||||
<string name="pref_webdav_password">كلمة المرور</string>
|
||||
<string name="pref_webdav_password_summ">ادخل كلمة المرور حساب WebDAV الخاص بك</string>
|
||||
<string name="pref_webdav_folder">مجلد بيانات WebDAV</string>
|
||||
<string name="pref_webdav_folder_summ">سيقوم التطبيق بتخزين البيانات في هذا المجلد</string>
|
||||
<string name="downloaded_data">البيانات التي تم تنزيلها</string>
|
||||
<string name="chapters_from_database">الفصول والسجل من قاعدة البيانات</string>
|
||||
<string name="action_manual_install">تثبيت يدوي</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -96,8 +96,10 @@
|
|||
<string name="pref_library_filter_categories_details">Entries in excluded categories will not be shown even if they are also in included categories.</string>
|
||||
<string name="updating">Updating</string>
|
||||
<string name="pref_show_empty_categories_search">Show empty categories during search/filtering</string>
|
||||
<string name="pref_sync_manga_on_add">Sync manga when adding to library</string>
|
||||
<string name="pref_sync_manga_on_add_description">Automatically fetches the metadata and chapter list of a new manga when adding it to the library</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add">Fetch metadata when adding to library</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add_description">Automatically fetches the metadata of a new manga when adding it to the library</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add">Fetch chapters when adding to library</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add_description">Automatically fetches the chapter list of a new manga when adding it to the library</string>
|
||||
<!-- Extension section -->
|
||||
<string name="extensions_page_need_refresh">Refresh extension page to update list.</string>
|
||||
<string name="extensions_page_more">More extensions...</string>
|
||||
|
|
|
|||
|
|
@ -172,12 +172,14 @@
|
|||
<string name="action_filter_unfinished_chapter">Di natapos na kabanata</string>
|
||||
<string name="action_filter_non_library_entries">Mga entry na wala sa aklatan</string>
|
||||
<string name="custom_theme_palette_casual">Kaswal</string>
|
||||
<string name="pref_sync_manga_on_add">I-sync ang manga kapag nagdadagdag sa aklatan</string>
|
||||
<string name="pref_sync_manga_on_add_description">Awtomatikong kinukuha ang metadata at listahan ng kabanata ng isang bagong manga kapag idinagdag ito sa aklatan</string>
|
||||
<string name="pref_webdav_url">Server URL ng WebDAV</string>
|
||||
<string name="pref_webdav_url_summ">Ipasok ang address ng WebDAV server</string>
|
||||
<string name="pref_webdav_username_summ">Ipasok ang iyong username ng WebDAV account</string>
|
||||
<string name="pref_webdav_password_summ">Ipasok ang iyong password ng WebDAV account</string>
|
||||
<string name="pref_webdav_folder_summ">Ise-save ng aplikasyon ang data sa folder na ito</string>
|
||||
<string name="pref_discord_rpc">Rich Presence ng Discord</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add">Kunin ang metadata ng manga kapag idinadagdag sa aklatan</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add_description">Awtomatikong kunin at i-update ang metadata ng manga kapag idinagdag mo ito sa iyong aklatan</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add">Kunin ang mga kabanata kapag idinadagdag ang manga sa aklatan</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add_description">Awtomatikong i-scan ang source para sa mga kabanata kapag idinagdag ang manga sa iyong aklatan</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="action_source_search">Cerca nella sorgente</string>
|
||||
<string name="action_source_search">Cerca nella fonte</string>
|
||||
<string name="action_toggle_nsfw_only">Attiva/disattiva solo NSFW</string>
|
||||
<string name="action_bulk_select">Modalità selezione Bulk</string>
|
||||
<string name="action_bulk_select">Modalità selezione multipla</string>
|
||||
<string name="action_faq_and_guides">FAQ e guide</string>
|
||||
<string name="download_cache_renew_interval">Intervallo di rinnovo download cache</string>
|
||||
<string name="download_cache_renew_interval_manual">Manuale</string>
|
||||
|
|
@ -11,34 +11,34 @@
|
|||
<string name="download_cache_renew_interval_6hour">6 ore</string>
|
||||
<string name="download_cache_renew_interval_12hour">12 ore</string>
|
||||
<string name="download_cache_renew_interval_24hour">1 giorno</string>
|
||||
<string name="download_cache_renew_interval_info">Se impostato su \"manual\", aggiorna ogni manga o usando l\'azione \"Invalidate downloads index\" in \"Avanzate\"</string>
|
||||
<string name="download_cache_renew_interval_info">Se impostato su \"manual\", aggiorna ogni manga o usa l\'azione \"Invalidate downloads index\" in \"Avanzate\"</string>
|
||||
<string name="pref_clean_invalid_downloads">Pulisci download non validi</string>
|
||||
<string name="pref_clean_invalid_downloads_summary">Trova e rimuovi tutti i download, file, cartelle che non vengono salvate nella tua libreria</string>
|
||||
<string name="invalid_downloads_cleaned">Scaricamenti non validi puliti</string>
|
||||
<string name="pref_private_installer_warning">Evitare di utilizzare Installer Privato perché non è ancora stabile, porterà a un avvio e riavvio dell\'app molto lento.</string>
|
||||
<string name="whats_coming">Cosa succederà (presto)</string>
|
||||
<string name="action_allow_duplicate_manga">Consenti di farlo</string>
|
||||
<string name="action_skip_duplicate_manga">Salta esso</string>
|
||||
<string name="whats_coming">In arrivo (a breve)</string>
|
||||
<string name="action_allow_duplicate_manga">Consenti</string>
|
||||
<string name="action_skip_duplicate_manga">Salta</string>
|
||||
<string name="action_allow_all_duplicate_manga">Consenti tutto</string>
|
||||
<string name="action_skip_all_duplicate_manga">Salta tutto</string>
|
||||
<string name="update_check_try_now">Prova ora</string>
|
||||
<string name="update_check_notification_preview_available">In arrivo (presto) aggiornamenti!</string>
|
||||
<string name="update_check_notification_preview_available">Aggiornamenti in arrivo!</string>
|
||||
<string name="changelogs">Modifiche</string>
|
||||
<string name="related_mangas_more">Più come questi...</string>
|
||||
<string name="related_mangas_more">Opere simili...</string>
|
||||
<string name="too_many_in_feed">Troppe fonti nel tuo feed, non puoi aggiungere più del limite (20)</string>
|
||||
<string name="current_">Attuale: %1$s</string>
|
||||
<string name="pref_theme_cover_based">Tema basato sulla copertina</string>
|
||||
<string name="pref_theme_cover_based_style_tonalspot">Punto Tonale</string>
|
||||
<string name="pref_theme_cover_based_style_neutral">Neutro</string>
|
||||
<string name="pref_theme_cover_based_style_vibrant">Vibrante</string>
|
||||
<string name="pref_theme_cover_based_style_vibrant">Acceso</string>
|
||||
<string name="pref_theme_cover_based_style_expressive">Espressivo</string>
|
||||
<string name="pref_theme_cover_based_style_rainbow">Arcobaleno</string>
|
||||
<string name="pref_theme_cover_based_style_fruitsalad">Insalata Di Frutta</string>
|
||||
<string name="pref_theme_cover_based_style_monochrome">Monocromatico</string>
|
||||
<string name="pref_theme_cover_based_style_fidelity">Fedeltà</string>
|
||||
<string name="pref_theme_cover_based_style_content">Contenuto</string>
|
||||
<string name="action_search_for_source">Cerca la sorgente</string>
|
||||
<string name="action_library_search">Ricerca libreria</string>
|
||||
<string name="action_search_for_source">Cerca fonte</string>
|
||||
<string name="action_library_search">Cerca nella libreria</string>
|
||||
<string name="theme_cloudflare">Cloudflare</string>
|
||||
<string name="theme_cottoncandy">Zucchero filato</string>
|
||||
<string name="theme_matrix">Matrice</string>
|
||||
|
|
@ -55,8 +55,8 @@
|
|||
<string name="action_move_up">Sposta in alto</string>
|
||||
<string name="action_move_down">Sposta in basso</string>
|
||||
<string name="preload_library_cover_color">Ricarica il colore della copertina della libreria</string>
|
||||
<string name="saved_searches_add_feed">Ricerche salvate (Si prega di aggiungere al Feed)</string>
|
||||
<string name="saved_searches_delete">Ricerche salvate (Si prega di eliminare)</string>
|
||||
<string name="saved_searches_add_feed">Ricerche salvate (Tieni premuto per aggiungere al feed)</string>
|
||||
<string name="saved_searches_delete">Ricerche salvate (Tieni premuto per eliminare)</string>
|
||||
<string name="action_display_source_badge">Icona sorgente</string>
|
||||
<string name="action_display_language_icon">Usa icona lingua</string>
|
||||
<string name="action_confirm_color">Conferma Colore</string>
|
||||
|
|
@ -78,7 +78,7 @@
|
|||
<string name="sort_feed_confirmation">Vuoi ordinare i feed in modo alfabetico?</string>
|
||||
<string name="pref_custom_theme_style">Stile tema personalizzato</string>
|
||||
<string name="pref_theme_cover_based_style">Stile tema basato su copertina</string>
|
||||
<string name="custom_color_description">Scegli un colore del seme per il tema personalizzato</string>
|
||||
<string name="custom_color_description">Scegli un colore base per il tema personalizzato</string>
|
||||
<string name="custom_theme_palette_casual">Casuale</string>
|
||||
<string name="custom_theme_palette_sunset">Tramonto</string>
|
||||
<string name="custom_theme_palette_raspberry">Lampone</string>
|
||||
|
|
@ -122,5 +122,80 @@
|
|||
<string name="custom_theme_palette_outrun">Superare</string>
|
||||
<string name="custom_theme_palette_bubblegum">Gomma da masticare</string>
|
||||
<string name="custom_theme_palette_springfield">Springfield</string>
|
||||
<string name="action_manual_install">installazione manuale</string>
|
||||
<string name="action_manual_install">Installazione manuale</string>
|
||||
<string name="action_open_folder">Apri cartella</string>
|
||||
<string name="action_update">Aggiorna</string>
|
||||
<string name="action_filter_unfinished_manga">Manga incompleti</string>
|
||||
<string name="action_filter_unfinished_chapter">Capitolo incompleto</string>
|
||||
<string name="active_account">Account attivo</string>
|
||||
<string name="login_failed">Login fallito</string>
|
||||
<string name="discord_status_description">Sezione Discord</string>
|
||||
<string name="discord_accounts">Account Discord</string>
|
||||
<string name="no_accounts_found">Nessun account trovato</string>
|
||||
<string name="pref_category_discord_customization">Personalizzazione Discord</string>
|
||||
<string name="pref_discord_custom_message">Messaggio personalizzato</string>
|
||||
<string name="pref_discord_custom_message_summary">Messaggio personalizzato che verrà mostrato su Discord</string>
|
||||
<string name="pref_discord_dnd">⛔ Non disturbare</string>
|
||||
<string name="pref_discord_idle">🌙 Inattivo</string>
|
||||
<string name="pref_discord_incognito">Modalità incognita Discord</string>
|
||||
<string name="pref_discord_online">🟢 Online</string>
|
||||
<string name="pref_discord_show_buttons">Mostra pulsanti</string>
|
||||
<string name="pref_discord_show_progress">Mostra progressi</string>
|
||||
<string name="pref_discord_show_download_button">Mostra pulsante download</string>
|
||||
<string name="pref_discord_show_discord_button">Mostra pulsante Discord</string>
|
||||
<string name="action_filter_non_library_entries">Voci non in libreria</string>
|
||||
<string name="pref_discord_show_timestamp_summary">Mostra tempo di attività</string>
|
||||
<string name="discord_download_button">Leggi su %s</string>
|
||||
<string name="pref_discord_show_progress_summary">Mostra capitolo corrente</string>
|
||||
<string name="pref_discord_show_timestamp">Mostra tempo</string>
|
||||
<string name="pref_discord_show_download_button_summary">Mostra pulsante per scaricare l\'applicazione</string>
|
||||
<string name="scale_type_double">Doppio zoom</string>
|
||||
<string name="pref_top_align_cover">Allinea copertina in alto</string>
|
||||
<string name="updating">Aggiornamento in corso</string>
|
||||
<string name="pref_webdav_username">Nome utente</string>
|
||||
<string name="pref_webdav_username_summ">Inserisci il nome utente del tuo account WebDAV</string>
|
||||
<string name="pref_webdav_password">Password</string>
|
||||
<string name="pref_webdav_password_summ">Inserisci la password del tuo account WebDAV</string>
|
||||
<string name="pref_webdav_folder">Cartella dati WebDAV</string>
|
||||
<string name="pref_webdav_folder_summ">L\'applicazione salverà i dati in questa cartella</string>
|
||||
<string name="pref_webdav_url">WebDAV Server URL</string>
|
||||
<string name="pref_webdav_url_summ">Inserisci l\'indirizzo server di WebDAV</string>
|
||||
<string name="downloaded_data">Dati scaricati</string>
|
||||
<string name="migrationConfigScreen_smartSearchSingleEntryTitle">Usa ricerca intelligente</string>
|
||||
<string name="sponsor_me">Sponsorizzami</string>
|
||||
<string name="connections_discord">Discord</string>
|
||||
<string name="pref_discord_status">Stato di Discord</string>
|
||||
<string name="show_chapters_titles_title">Mostra Titoli Capitolo</string>
|
||||
<string name="show_chapters_titles_subtitle">Mostra titolo capitolo corrente</string>
|
||||
<string name="special_services">Servizi speciali</string>
|
||||
<string name="pref_discord_show_discord_button_summary">Mostra pulsante per entrare nel server Discord</string>
|
||||
<string name="pref_category_connections">Collegamenti</string>
|
||||
<string name="pref_discord_configuration">Apri per configurare Discord RPC</string>
|
||||
<string name="error_opening_folder">Impossibile aprire cartella</string>
|
||||
<string name="pref_show_empty_categories_search">Mostra categorie vuote durante ricerca/filtraggio</string>
|
||||
<string name="chapters_from_database">Capitoli e cronologia dal database</string>
|
||||
<string name="job_failed_schedule_update_check">Pianificazione aggiornamento automatico libreria fallita: %s</string>
|
||||
<string name="job_failed_schedule_backup_check">Pianificazione backup automatico fallito: %s</string>
|
||||
<string name="pref_paged_disable_zoom_in">Disattiva ingrandimento</string>
|
||||
<string name="pref_landscape_zoom_type">Modalità zoom panoramico</string>
|
||||
<string name="scale_type_16_9">16:9</string>
|
||||
<string name="scale_type_20_9">20:9</string>
|
||||
<string name="scale_type_4_3">4:3</string>
|
||||
<string name="scale_type_3_2">3:2</string>
|
||||
<string name="pref_top_align_cover_summary">Mostra copertina allineata in alto accanto alle informazioni sul manga</string>
|
||||
<string name="theme_doom">Doom</string>
|
||||
<string name="pref_library_filter_categories_details">Le voci nelle categorie escluse non verranno mostrate anche se presenti nelle categorie incluse.</string>
|
||||
<string name="web_dav">WebDAV</string>
|
||||
<string name="migrationConfigScreen_smartSearchSingleEntrySubtitle">Se abilitato, seleziona automaticamente il risultato con la corrispondenza migliore. Altrimenti, mostra tutti i risultati della ricerca per la selezione manuale.</string>
|
||||
<string name="pref_connections_summary">Discord, altri in arrivo...</string>
|
||||
<string name="connections_discord_info">Questo servizio Discord funziona tramite registrazione dei token. Utilizzalo a tuo rischio e pericolo. I token vengono salvati localmente sul tuo dispositivo e non vengono condivisi da %s.</string>
|
||||
<string name="pref_discord_rpc">Discord Rich Presence</string>
|
||||
<string name="pref_enable_discord_rpc">Abilita Discord Rich Presence</string>
|
||||
<string name="pref_discord_incognito_summary">Ignora se la modalità di navigazione in incognito è attiva</string>
|
||||
<string name="pref_discord_incognito_categories_details">Le voci in categorie incluse non verranno visualizzate su Discord</string>
|
||||
<string name="discord_app_description">Komikku - Un\'app per leggere manga</string>
|
||||
<string name="pref_discord_show_buttons_summary">Visualizza i pulsanti per scaricare l\'app o unirti al server Discord</string>
|
||||
<string name="pref_webtoon_scale_type">Regolazione automatica per la visualizzazione su schermo panoramico</string>
|
||||
<string name="pref_track_on_add_library">Apri il menu tracciamento quando aggiungi una voce alla libreria</string>
|
||||
<string name="discord_status_browsing">Naviga</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="download_cache_renew_interval_1hour">1 시간</string>
|
||||
<string name="download_cache_renew_interval_2hour">2 시간</string>
|
||||
<string name="download_cache_renew_interval_6hour">6 시간</string>
|
||||
<string name="download_cache_renew_interval_12hour">12 시간</string>
|
||||
<string name="download_cache_renew_interval_24hour">1 일</string>
|
||||
<string name="theme_cottoncandy">솜사탕</string>
|
||||
<string name="theme_doom">둠</string>
|
||||
<string name="theme_matrix">매트릭스</string>
|
||||
<string name="theme_mocha">모카</string>
|
||||
<string name="theme_sapphire">사파이어</string>
|
||||
<string name="download_cache_renew_interval_1hour">1 시간</string>
|
||||
<string name="download_cache_renew_interval_2hour">2 시간</string>
|
||||
<string name="download_cache_renew_interval_6hour">6 시간</string>
|
||||
<string name="download_cache_renew_interval_12hour">12 시간</string>
|
||||
<string name="download_cache_renew_interval_24hour">1 일</string>
|
||||
<string name="theme_cottoncandy">솜사탕</string>
|
||||
<string name="theme_doom">둠</string>
|
||||
<string name="theme_matrix">매트릭스</string>
|
||||
<string name="theme_mocha">모카</string>
|
||||
<string name="theme_sapphire">사파이어</string>
|
||||
<string name="action_remove_merged">라이브러리에서 원본 작품을 삭제하시겠습니까?</string>
|
||||
<string name="action_search_for_source">소스 검색</string>
|
||||
<string name="action_source_search">소스 내 검색</string>
|
||||
<string name="action_library_search">라이브러리 검색</string>
|
||||
<string name="action_toggle_nsfw_only">NSFW만 표시</string>
|
||||
<string name="action_bulk_select">일괄 선택 모드</string>
|
||||
<string name="action_faq_and_guides">FAQ 및 가이드</string>
|
||||
<string name="action_move_up">위로 이동</string>
|
||||
<string name="action_move_down">아래로 이동</string>
|
||||
<string name="action_confirm_color">색상 확인</string>
|
||||
<string name="action_hide">숨기기</string>
|
||||
<string name="action_show_hidden_categories">숨겨진 카테고리 표시</string>
|
||||
<string name="action_panorama_cover">파노라마 표지</string>
|
||||
<string name="action_open_folder">폴더 열기</string>
|
||||
<string name="action_update">업데이트</string>
|
||||
<string name="action_filter_unfinished_manga">미완결 manga</string>
|
||||
<string name="action_filter_unfinished_chapter">미완결 챕터</string>
|
||||
<string name="action_filter_non_library_entries">라이브러리 없는 작품</string>
|
||||
<string name="action_copy_to_clipboard_first_page">1페이지 클립보드에 복사</string>
|
||||
<string name="action_copy_to_clipboard_second_page">2페이지 클립보드에 복사</string>
|
||||
<string name="action_copy_to_clipboard_combined_page">결합 페이지 클립보드에 복사</string>
|
||||
<string name="pref_viewer_nav_smaller_tap_zone">탭 영역 축소</string>
|
||||
<string name="error_opening_folder">폴더 열기 실패</string>
|
||||
<string name="pref_pinch_to_zoom">핀치로 줌</string>
|
||||
<string name="pref_paged_disable_zoom_in">줌인 비활성화</string>
|
||||
<string name="pref_landscape_zoom_type">넓은 이미지 줌 모드</string>
|
||||
<string name="scale_type_16_9">16:9</string>
|
||||
<string name="scale_type_20_9">20:9</string>
|
||||
<string name="scale_type_4_3">4:3</string>
|
||||
<string name="scale_type_3_2">3:2</string>
|
||||
<string name="pref_custom_theme_style">커스텀 테마 스타일</string>
|
||||
<string name="pref_manga_info">Manga 정보</string>
|
||||
<string name="pref_theme_cover_based">표지 기반 테마</string>
|
||||
<string name="pref_theme_cover_based_style">표지 기반 테마 스타일</string>
|
||||
<string name="pref_panorama_cover">파노라마 표지</string>
|
||||
<string name="pref_panorama_cover_summary">넓은 이미지인 경우 가로 모드로 표지 표시</string>
|
||||
<string name="pref_panorama_cover_flow">파노라마 표지 흐름</string>
|
||||
<string name="pref_panorama_cover_flow_summary">글로벌 검색 & 제안에 파노라마 표지 표시</string>
|
||||
<string name="pref_top_align_cover">표지를 상단에 정렬</string>
|
||||
<string name="pref_top_align_cover_summary">Manga 정보와 표지를 상단에 정렬하여 표시</string>
|
||||
<string name="action_display_comfortable_grid_panorama">파노라마 편안한 그리드</string>
|
||||
<string name="pref_expand_related_mangas">추천 펼치기</string>
|
||||
<string name="pref_expand_related_mangas_summary">비활성화하면 추천을 자동으로 불러오거나 펼쳐서 표시하지 않습니다</string>
|
||||
<string name="put_related_mangas_in_overflow">오버플로우 메뉴 추천</string>
|
||||
<string name="put_related_mangas_in_overflow_summary">추천 버튼을 엔트리 페이지 대신 오버플로우 메뉴에 배치</string>
|
||||
<string name="pref_show_home_on_related_mangas">홈 메뉴 추천</string>
|
||||
<string name="pref_show_home_on_related_mangas_summary">추천을 탐색하는 동안 홈으로 바로 이동하는 아이콘 표시</string>
|
||||
<string name="theme_cloudflare">Cloudflare</string>
|
||||
<string name="pref_custom_color">팔레트 개인화</string>
|
||||
<string name="custom_color_description">커스텀 테마용 시드 색상 선택</string>
|
||||
<string name="theme_custom">커스텀 테마</string>
|
||||
<string name="pref_show_updating_progress_banner">업데이트 중 배너 표시</string>
|
||||
<string name="action_display_source_badge">소스 아이콘</string>
|
||||
<string name="action_display_language_icon">언어 아이콘 사용</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -204,6 +204,10 @@
|
|||
<string name="action_filter_unfinished_manga">Незаконченные тайтлы</string>
|
||||
<string name="action_filter_unfinished_chapter">Незаконченные главы</string>
|
||||
<string name="action_filter_non_library_entries">Тайтлы не в библиотеке</string>
|
||||
<string name="pref_sync_manga_on_add">Синхронизировать тайтлы при добавлении в библиотеку</string>
|
||||
<string name="pref_sync_manga_on_add_description">Автоматически загружает метаданные и список глав при добавлении в библиотеку</string>
|
||||
<string name="migrationConfigScreen_smartSearchSingleEntryTitle">Использовать умный поиск</string>
|
||||
<string name="migrationConfigScreen_smartSearchSingleEntrySubtitle">Если включено, автоматически выбирает наиболее совпадающий результат. Иначе, показывает все результаты поиска для ручного выбора.</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add">Получать метаданные манги при добавлении</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add_description">Автоматически загружать обложку, описание и другую информацию при добавлении манги в библиотеку</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add">Получать главы при добавлении</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add_description">Автоматически обновлять список глав при добавлении манги в библиотеку</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -195,6 +195,19 @@
|
|||
<string name="action_filter_unfinished_manga">尚未讀完</string>
|
||||
<string name="action_filter_unfinished_chapter">未讀完章節</string>
|
||||
<string name="action_filter_non_library_entries">未收藏項目</string>
|
||||
<string name="pref_sync_manga_on_add">新增至書庫時自動同步</string>
|
||||
<string name="pref_sync_manga_on_add_description">將新漫畫加入書庫時,自動取得其元資料及章節列表</string>
|
||||
<string name="web_dav">WebDAV</string>
|
||||
<string name="pref_webdav_url">WebDAV 伺服器 URL</string>
|
||||
<string name="pref_webdav_url_summ">輸入 WebDAV 伺服器地址</string>
|
||||
<string name="pref_webdav_username">使用者名稱</string>
|
||||
<string name="pref_webdav_username_summ">輸入你的 WebDAV 帳戶使用者名稱</string>
|
||||
<string name="pref_webdav_password">密碼</string>
|
||||
<string name="pref_webdav_password_summ">輸入你的 WebDAV 帳戶密碼</string>
|
||||
<string name="pref_webdav_folder">WebDAV 資料夾</string>
|
||||
<string name="pref_webdav_folder_summ">應用將把資料儲存在這個資料夾中</string>
|
||||
<string name="migrationConfigScreen_smartSearchSingleEntryTitle">使用智慧搜尋</string>
|
||||
<string name="migrationConfigScreen_smartSearchSingleEntrySubtitle">若已啟用,自動選取最佳匹配結果,否則顯示所有搜尋結果供手動選擇</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add">新增至書櫃時更新漫畫資料</string>
|
||||
<string name="pref_fetch_manga_metadata_on_add_description">在將作品加入書櫃後,自動抓取標題、作者等最新漫畫資訊</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add">新增至書櫃時更新章節列表</string>
|
||||
<string name="pref_fetch_manga_chapters_on_add_description">在將作品加入書櫃後,自動抓取最新章節列表</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -29,15 +29,12 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||
|
||||
var mediaId: String? = null
|
||||
|
||||
var mediaServer: Int? = null
|
||||
|
||||
var japaneseTitle by titleDelegate(TITLE_TYPE_JAPANESE)
|
||||
var englishTitle by titleDelegate(TITLE_TYPE_ENGLISH)
|
||||
var shortTitle by titleDelegate(TITLE_TYPE_SHORT)
|
||||
|
||||
var coverImageType: String? = null
|
||||
var pageImageTypes: List<String> = emptyList()
|
||||
var thumbnailImageType: String? = null
|
||||
var coverImageUrl: String? = null
|
||||
var pageImagePreviewUrls: List<String> = emptyList()
|
||||
|
||||
var scanlator: String? = null
|
||||
|
||||
|
|
@ -46,16 +43,6 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||
override fun createMangaInfo(manga: SManga): SManga {
|
||||
val key = nhId?.let { nhIdToPath(it) }
|
||||
|
||||
val cover = if (mediaId != null) {
|
||||
// Default media server for cover is always 1 (see in page header)
|
||||
val server = mediaServer ?: 1
|
||||
typeToExtension(coverImageType)?.let {
|
||||
"https://t$server.nhentai.net/galleries/$mediaId/cover.$it"
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val title = when (preferredTitle) {
|
||||
TITLE_TYPE_SHORT -> shortTitle ?: englishTitle ?: japaneseTitle ?: manga.title
|
||||
0, TITLE_TYPE_ENGLISH -> englishTitle ?: japaneseTitle ?: shortTitle ?: manga.title
|
||||
|
|
@ -88,7 +75,7 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||
|
||||
return manga.copy(
|
||||
url = key ?: manga.url,
|
||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
||||
thumbnail_url = coverImageUrl ?: manga.thumbnail_url,
|
||||
title = title,
|
||||
artist = group ?: manga.artist,
|
||||
author = artist ?: manga.artist,
|
||||
|
|
@ -116,9 +103,8 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||
getItem(japaneseTitle) { stringResource(SYMR.strings.japanese_title) },
|
||||
getItem(englishTitle) { stringResource(SYMR.strings.english_title) },
|
||||
getItem(shortTitle) { stringResource(SYMR.strings.short_title) },
|
||||
getItem(coverImageType) { stringResource(SYMR.strings.cover_image_file_type) },
|
||||
getItem(pageImageTypes.size) { stringResource(SYMR.strings.page_count) },
|
||||
getItem(thumbnailImageType) { stringResource(SYMR.strings.thumbnail_image_file_type) },
|
||||
getItem(coverImageUrl) { stringResource(SYMR.strings.thumbnail_url) },
|
||||
getItem(pageImagePreviewUrls.size) { stringResource(SYMR.strings.page_count) },
|
||||
getItem(scanlator) { stringResource(MR.strings.scanlator) },
|
||||
)
|
||||
}
|
||||
|
|
@ -137,15 +123,6 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||
private const val NHENTAI_GROUP_NAMESPACE = "group"
|
||||
const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
||||
|
||||
fun typeToExtension(t: String?) =
|
||||
when (t) {
|
||||
"p" -> "png"
|
||||
"j" -> "jpg"
|
||||
"g" -> "gif"
|
||||
"w" -> "webp"
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun nhUrlToId(url: String) =
|
||||
url.split("/").last { it.isNotBlank() }.toLong()
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import java.nio.charset.StandardCharsets
|
|||
import kotlin.time.Duration.Companion.days
|
||||
import tachiyomi.domain.source.model.Source as DomainSource
|
||||
|
||||
actual class LocalSource(
|
||||
class LocalSource(
|
||||
private val context: Context,
|
||||
private val fileSystem: LocalSourceFileSystem,
|
||||
private val coverManager: LocalCoverManager,
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package tachiyomi.source.local
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.UnmeteredSource
|
||||
|
||||
expect class LocalSource : CatalogueSource, UnmeteredSource
|
||||
Loading…
Reference in a new issue