Compare commits

...

12 commits

Author SHA1 Message Date
littlecodedragon
af860b5f9d Merge remote-tracking branch 'upstream/master'
Some checks failed
Forgejo Release Builder / release (push) Failing after 27m23s
2026-05-06 16:06:29 +02:00
Cuong-Tran
bdf81596e9
chore: Avoid race conditions and improve bulk selection (#1606)
* avoid race conditions

* Improve Bulk selection

* Consolidate selection mode updates and list clearing into a single state update.
2026-04-23 16:30:55 +07:00
Cuong-Tran
d8eb71e004
chore(UI): Use remember getUiModel (#1605)
* use remember getUiModel

* update headerIndexes to use remember for state management

* Refactor LibraryUpdateErrorScreen to improve state management

*   Move header index calculation from the screen model to the presentation layer.
*   Optimize UI model and header index memoization using `remember(state)`.
*   Remove redundant `LaunchedEffect` and simplify scroll-to-index logic.

* Refactor LibraryUpdateErrorScreen to enhance state management with derivedStateOf
2026-04-23 15:19:54 +07:00
Cuong-Tran
bf9b663581 Optimize database indexes and clean up schema (#1603)
* Update `excluded_scanlators` index to a composite index on `manga_id` and `scanlator`.
* Convert `idx_history_last_read` into a partial index for entries where `last_read > 0`.
* Remove redundant `idx_manga_sync_manga_id` index.
* Remove unused `last_modified_at` column and its associated trigger from `mangas_categories`.
* Sync migration files with schema changes.
2026-04-23 00:02:39 +07:00
Luca Auer
8bb8d7ccb7 Add missing indexes to improve database query performance (mihonapp/mihon#2950)
(cherry picked from commit cae9fbf3213987e7d263845431dfac10a2ecb3b0)
2026-04-23 00:02:39 +07:00
MediocreLegion
80ea323281
fix(delegate): migrate NH to the v2 api (#1582)
* fix(delegate): migrate NH to the v2 api

* bump versionId

* remove versionId requirement

* gemini review suggestions

* remove dead code

* remove unused imports

* linging

* unneeded comment

* fix(delegate): migrate NH to the v2 api (jobobby04/TachiyomiSY#1581)

* fix(delegate): migrate NH to the v2 api

* remove extra comment

* remove redundant data

* linting

* Code cleanup

---------

Co-authored-by: Jobobby04 <jobobby04@users.noreply.github.com>
(cherry picked from commit eec1236b8b5904b46d79bb716e29f073c6fe9b34)

* close `response`

* Avoid empty server list

---------

Co-authored-by: Jobobby04 <jobobby04@users.noreply.github.com>
Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com>
2026-04-22 13:31:05 +07:00
Copilot
ee7fb26790
fix: remove expect/actual for LocalSource in single-target KMP module (#1602)
Agent-Logs-Url: https://github.com/komikku-app/komikku/sessions/5bf2689f-d906-47af-881b-4556f828a3a6

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: cuong-tran <16017808+cuong-tran@users.noreply.github.com>
2026-04-22 12:18:16 +07:00
Cuong-Tran
6f020399d8
chore(ci): Add SSH key setup for Codeberg repository mirroring (#1595) 2026-04-16 16:22:27 +07:00
Cuong-Tran
26d3be95e3
feat(UI): Mask WebDAV password while typing (#1564)
* Refactor

* feat(UI): Mask WebDAV password while typing

* fix: allow space as password

* use coroutines for asynchronous setting
2026-04-16 14:01:49 +07:00
Cuong-Tran
fd60bc7f13
fix(MangaDex): Fix nullability issues in track creation for MangaDex (#1527)
* fix(trackers-mangadex): Handle nullability in `createMdListTrack`

* Return null instead of throwing exception in initial track creation

* Improve error handling in `createMdListTrack`

* Change exception to return null when track not found

* handle nullability in MangaDex tracking
2026-04-16 12:52:51 +07:00
Cuong-Tran
6f06974524
feat: split "sync on add" into separate metadata and chapter fetch preferences (#1563)
* feat: split "sync on add" into separate metadata and chapter fetch preferences

* refactor: remove redundant SnackbarHostState and error snackbars when auto sync entries

* clean other languages strings
2026-03-17 13:09:48 +07:00
Weblate (bot)
edaf975e21
Translations update from Hosted Weblate (#1523)
* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 100.0% (208 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/zh_Hant/

* Translated using Weblate (Italian)

Currently translated at 94.7% (197 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/it/

* Translated using Weblate (Italian)

Currently translated at 95.1% (198 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/it/

* Translated using Weblate (Arabic)

Currently translated at 74.5% (155 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/ar/

* Translated using Weblate (Korean)

Currently translated at 30.7% (64 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/ko/

* Translated using Weblate (Russian)

Currently translated at 100.0% (208 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/ru/

* Translated using Weblate (Italian)

Currently translated at 95.1% (198 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/it/

* Translated using Weblate (Arabic)

Currently translated at 74.5% (155 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/ar/

* Translated using Weblate (Korean)

Currently translated at 30.7% (64 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/ko/

* Translated using Weblate (Italian)

Currently translated at 95.6% (199 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/it/

* Translated using Weblate (Arabic)

Currently translated at 74.5% (155 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/ar/

* Translated using Weblate (Arabic)

Currently translated at 74.5% (155 of 208 strings)

Translation: Komikku-App/Komikku
Translate-URL: https://hosted.weblate.org/projects/komikku-app/komikku/ar/

---------

Co-authored-by: ɴᴇᴋᴏ <s99095lkjjim@gmail.com>
Co-authored-by: Giulia <giuliachen6562@gmail.com>
Co-authored-by: pasquale ruotolo <pachiruo@gmail.com>
Co-authored-by: ZiadSenior <Ziadsenior@yahoo.com>
Co-authored-by: 안세훈 <on9686@gmail.com>
Co-authored-by: amigo browser <juniperforest1@proton.me>
Co-authored-by: Cuong Tran <cuongtran.tm@gmail.com>
2026-03-17 08:34:25 +07:00
38 changed files with 454 additions and 261 deletions

View file

@ -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"

View file

@ -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)
}

View file

@ -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)) },

View file

@ -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,

View file

@ -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) {

View file

@ -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),

View file

@ -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 = {
scope.launch {
syncPreferences.clientAPIKey().set(it)
}
true
},
icon = null,
@ -808,17 +811,28 @@ object SettingsDataScreen : SearchableSettings {
true
},
),
Preference.PreferenceItem.EditTextPreference(
preference = syncPreferences.webDavPassword(),
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),
onValueChanged = { newValue ->
scope.launch {
syncPreferences.webDavPassword().set(newValue)
}
true
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 {

View file

@ -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 <--
),

View file

@ -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,

View file

@ -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,

View file

@ -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 <--

View file

@ -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 {

View file

@ -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,14 +197,22 @@ 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(
@ -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:"
}
}

View file

@ -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)
if (fetchMetadataOnAdd) {
val remoteMetadata = source.getMangaDetails(sManga)
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
updateManga.awaitUpdateFromSource(manga, remoteManga, false, coverCache)
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)
if (fetchMetadataOnAdd) {
val remoteMetadata = source.getMangaDetails(sManga)
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
updateManga.awaitUpdateFromSource(manga, remoteManga, false, coverCache)
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}")
}
}
}

View file

@ -149,11 +149,10 @@ class MigrateMangaScreenModel(
selectedMangaIds.addOrRemove(it.manga.id, selected)
it.copy(selected = selected)
}
state.copy(titleList = newItems.toImmutableList())
}
selectedPositions[0] = -1
selectedPositions[1] = -1
state.copy(titleList = newItems.toImmutableList())
}
}
fun invertSelection() {
@ -162,10 +161,10 @@ class MigrateMangaScreenModel(
selectedMangaIds.addOrRemove(it.manga.id, !it.selected)
it.copy(selected = !it.selected)
}
state.copy(titleList = newItems.toImmutableList())
}
selectedPositions[0] = -1
selectedPositions[1] = -1
state.copy(titleList = newItems.toImmutableList())
}
}
// KMK <--

View file

@ -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)
if (fetchMetadataOnAdd) {
val remoteMetadata = source.getMangaDetails(sManga)
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
updateManga.awaitUpdateFromSource(manga, remoteManga, false, coverCache)
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}")
}
}
}
}

View file

@ -129,11 +129,10 @@ class LibraryUpdateErrorScreenModel(
selectedErrorIds.addOrRemove(it.error.errorId, selected)
it.copy(selected = selected)
}
state.copy(items = newItems)
}
selectedPositions[0] = -1
selectedPositions[1] = -1
state.copy(items = newItems)
}
}
fun invertSelection() {
@ -142,10 +141,10 @@ class LibraryUpdateErrorScreenModel(
selectedErrorIds.addOrRemove(it.error.errorId, !it.selected)
it.copy(selected = !it.selected)
}
state.copy(items = newItems)
}
selectedPositions[0] = -1
selectedPositions[1] = -1
state.copy(items = newItems)
}
}
@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

View file

@ -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,13 +1881,15 @@ class MangaScreenModel(
}
// SY -->
private suspend fun createMdListTrack(): Track {
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("Could not create initial track")
?: throw IllegalArgumentException("Entry does not belong to MangaDex")
val track = trackerManager.mdList.createInitialTracker(state.manga, mdManga)
.toDomainTrack(false)!!
.toDomainTrack(false)
?: throw IllegalStateException("Could not create initial track")
insertTrack.await(track)
/* KMK -->
return TrackItem(
@ -1895,6 +1898,10 @@ class MangaScreenModel(
)
KMK <-- */
return getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id }
} catch (e: Exception) {
xLogE("Failed to create MangaDex track", e)
return null
}
}
// SY <--

View file

@ -461,11 +461,12 @@ class UpdatesScreenModel(
selectedChapterIds.addOrRemove(it.update.chapterId, selected)
it.copy(selected = selected)
}
state.copy(items = newItems.toPersistentList())
}
// KMK -->
selectedPositions[0] = -1
selectedPositions[1] = -1
// KMK <--
state.copy(items = newItems.toPersistentList())
}
}
fun invertSelection() {
@ -474,10 +475,12 @@ class UpdatesScreenModel(
selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected)
it.copy(selected = !it.selected)
}
state.copy(items = newItems.toPersistentList())
}
// KMK -->
selectedPositions[0] = -1
selectedPositions[1] = -1
// KMK <--
state.copy(items = newItems.toPersistentList())
}
}
fun setDialog(dialog: Dialog?) {

View file

@ -124,7 +124,7 @@ class BrowseRecommendsScreen(
scrollBehavior = scrollBehavior,
// KMK -->
toggleSelectionMode = {
bulkFavoriteScreenModel.toggleSelectionMode()
bulkFavoriteScreenModel.toggleSelectionMode(true)
}.takeIf { !isExternalSource },
isRunning = bulkFavoriteState.isRunning,
// KMK <--

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View 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;

View file

@ -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(

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -10,4 +10,58 @@
<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">글로벌 검색 &amp; 제안에 파노라마 표지 표시</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>

View file

@ -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>

View file

@ -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>

View file

@ -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()

View file

@ -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,

View file

@ -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