Compare commits
12 commits
3b08f156d5
...
af860b5f9d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af860b5f9d | ||
|
|
bdf81596e9 | ||
|
|
d8eb71e004 | ||
|
|
bf9b663581 | ||
|
|
8bb8d7ccb7 | ||
|
|
80ea323281 | ||
|
|
ee7fb26790 | ||
|
|
6f020399d8 | ||
|
|
26d3be95e3 | ||
|
|
fd60bc7f13 | ||
|
|
6f06974524 | ||
|
|
edaf975e21 |
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)
|
- uses: actions/checkout@v4 # Forgejo-compatible (node20)
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
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
|
- uses: pixta-dev/repository-mirroring-action@674e65a7d483ca28dafaacba0d07351bdcc8bd75 # v1.1.1
|
||||||
with:
|
with:
|
||||||
target_repo_url: "git@codeberg.org:cuong-tran/komikku.git"
|
target_repo_url: "git@codeberg.org:cuong-tran/komikku.git"
|
||||||
|
|
|
||||||
|
|
@ -200,7 +200,7 @@ class SyncChaptersWithSource(
|
||||||
bookmark = chapter.chapterNumber in deletedBookmarkedChapterNumbers,
|
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 {
|
deletedChapterNumberDateFetchMap[chapter.chapterNumber]?.let {
|
||||||
chapter = chapter.copy(dateFetch = it)
|
chapter = chapter.copy(dateFetch = it)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ fun Screen.BulkFavoriteDialogs(
|
||||||
AddDuplicateMangaDialog(
|
AddDuplicateMangaDialog(
|
||||||
dialog = dialog,
|
dialog = dialog,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
state = bulkFavoriteState,
|
|
||||||
onDismiss = bulkFavoriteScreenModel::dismissDialog,
|
onDismiss = bulkFavoriteScreenModel::dismissDialog,
|
||||||
stopRunning = bulkFavoriteScreenModel::stopRunning,
|
stopRunning = bulkFavoriteScreenModel::stopRunning,
|
||||||
toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode,
|
toggleSelectionMode = bulkFavoriteScreenModel::toggleSelectionMode,
|
||||||
|
|
@ -134,10 +133,9 @@ private fun Screen.ShowMigrateDialog(
|
||||||
private fun AddDuplicateMangaDialog(
|
private fun AddDuplicateMangaDialog(
|
||||||
dialog: Dialog.AddDuplicateManga,
|
dialog: Dialog.AddDuplicateManga,
|
||||||
navigator: Navigator?,
|
navigator: Navigator?,
|
||||||
state: BulkFavoriteScreenModel.State,
|
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
stopRunning: () -> Unit,
|
stopRunning: () -> Unit,
|
||||||
toggleSelectionMode: () -> Unit,
|
toggleSelectionMode: (Boolean) -> Unit,
|
||||||
addFavorite: (Manga) -> Unit,
|
addFavorite: (Manga) -> Unit,
|
||||||
showMigrateDialog: (manga: Manga, duplicate: Manga) -> Unit,
|
showMigrateDialog: (manga: Manga, duplicate: Manga) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
@ -147,9 +145,7 @@ private fun AddDuplicateMangaDialog(
|
||||||
duplicates = dialog.duplicates,
|
duplicates = dialog.duplicates,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
if (state.selectionMode) {
|
toggleSelectionMode(false)
|
||||||
toggleSelectionMode()
|
|
||||||
}
|
|
||||||
addFavorite(dialog.manga)
|
addFavorite(dialog.manga)
|
||||||
},
|
},
|
||||||
onOpenManga = { navigator?.push(MangaScreen(it.id)) },
|
onOpenManga = { navigator?.push(MangaScreen(it.id)) },
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
|
@ -39,6 +38,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.libraryUpdateError.components.LibraryUpdateErrorUiModel
|
||||||
import eu.kanade.presentation.libraryUpdateError.components.libraryUpdateErrorUiItems
|
import eu.kanade.presentation.libraryUpdateError.components.libraryUpdateErrorUiItems
|
||||||
import eu.kanade.presentation.manga.components.Button
|
import eu.kanade.presentation.manga.components.Button
|
||||||
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem
|
import eu.kanade.tachiyomi.ui.libraryUpdateError.LibraryUpdateErrorItem
|
||||||
|
|
@ -75,31 +75,33 @@ fun LibraryUpdateErrorScreen(
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
val headerIndexes = remember { mutableStateOf<List<Int>>(emptyList()) }
|
val uiModels = remember(state) { state.getUiModel() }
|
||||||
LaunchedEffect(state) {
|
val headerIndexes = remember(state) {
|
||||||
headerIndexes.value = state.getHeaderIndexes()
|
uiModels.withIndex()
|
||||||
|
.filter { it.value is LibraryUpdateErrorUiModel.Header }
|
||||||
|
.map { it.index }
|
||||||
}
|
}
|
||||||
|
|
||||||
val enableScrollToTop by remember {
|
val enableScrollToTop by remember(listState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
listState.firstVisibleItemIndex > 0
|
listState.firstVisibleItemIndex > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val enableScrollToBottom by remember {
|
val enableScrollToBottom by remember(listState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
listState.canScrollForward
|
listState.canScrollForward
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val enableScrollToPrevious by remember {
|
val enableScrollToPrevious by remember(headerIndexes, listState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
headerIndexes.value.any { it < listState.firstVisibleItemIndex }
|
headerIndexes.any { it < listState.firstVisibleItemIndex }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val enableScrollToNext by remember {
|
val enableScrollToNext by remember(headerIndexes, listState) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
headerIndexes.value.any { it > listState.firstVisibleItemIndex }
|
headerIndexes.any { it > listState.firstVisibleItemIndex }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +143,7 @@ fun LibraryUpdateErrorScreen(
|
||||||
scrollToPrevious = {
|
scrollToPrevious = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
listState.scrollToItem(
|
listState.scrollToItem(
|
||||||
state.getHeaderIndexes()
|
headerIndexes
|
||||||
.filter { it < listState.firstVisibleItemIndex }
|
.filter { it < listState.firstVisibleItemIndex }
|
||||||
.maxOrNull() ?: 0,
|
.maxOrNull() ?: 0,
|
||||||
)
|
)
|
||||||
|
|
@ -150,7 +152,7 @@ fun LibraryUpdateErrorScreen(
|
||||||
scrollToNext = {
|
scrollToNext = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
listState.scrollToItem(
|
listState.scrollToItem(
|
||||||
state.getHeaderIndexes()
|
headerIndexes
|
||||||
.filter { it > listState.firstVisibleItemIndex }
|
.filter { it > listState.firstVisibleItemIndex }
|
||||||
.minOrNull() ?: 0,
|
.minOrNull() ?: 0,
|
||||||
)
|
)
|
||||||
|
|
@ -173,7 +175,7 @@ fun LibraryUpdateErrorScreen(
|
||||||
state = listState,
|
state = listState,
|
||||||
) {
|
) {
|
||||||
libraryUpdateErrorUiItems(
|
libraryUpdateErrorUiItems(
|
||||||
uiModels = state.getUiModel(),
|
uiModels = uiModels,
|
||||||
selectionMode = state.selectionMode,
|
selectionMode = state.selectionMode,
|
||||||
onErrorSelected = onErrorSelected,
|
onErrorSelected = onErrorSelected,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
|
|
||||||
|
|
@ -738,7 +738,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||||
subtitle = stringResource(SYMR.strings.data_saver_image_quality_summary),
|
subtitle = stringResource(SYMR.strings.data_saver_image_quality_summary),
|
||||||
enabled = dataSaver != DataSaver.NONE,
|
enabled = dataSaver != DataSaver.NONE,
|
||||||
),
|
),
|
||||||
kotlin.run {
|
run {
|
||||||
val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg()
|
val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg()
|
||||||
.collectAsState()
|
.collectAsState()
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
|
@ -808,7 +808,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
||||||
title = stringResource(SYMR.strings.log_level),
|
title = stringResource(SYMR.strings.log_level),
|
||||||
subtitle = stringResource(SYMR.strings.log_level_summary),
|
subtitle = stringResource(SYMR.strings.log_level_summary),
|
||||||
),
|
),
|
||||||
kotlin.run {
|
run {
|
||||||
var enableEncryptDatabase by rememberSaveable { mutableStateOf(false) }
|
var enableEncryptDatabase by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
if (enableEncryptDatabase) {
|
if (enableEncryptDatabase) {
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
||||||
enabled = sourcePreferences.relatedMangas().get(),
|
enabled = sourcePreferences.relatedMangas().get(),
|
||||||
),
|
),
|
||||||
// KMK <--
|
// KMK <--
|
||||||
kotlin.run {
|
run {
|
||||||
val count by sourcePreferences.sourcesTabCategories().collectAsState()
|
val count by sourcePreferences.sourcesTabCategories().collectAsState()
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(MR.strings.action_edit_categories),
|
title = stringResource(MR.strings.action_edit_categories),
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import eu.kanade.domain.sync.SyncPreferences
|
import eu.kanade.domain.sync.SyncPreferences
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
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.CreateBackupScreen
|
||||||
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
|
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
|
||||||
import eu.kanade.presentation.more.settings.screen.data.StorageInfo
|
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.SYNCYOMI.value to stringResource(SYMR.strings.syncyomi),
|
||||||
SyncManager.SyncService.GOOGLE_DRIVE.value to stringResource(SYMR.strings.google_drive),
|
SyncManager.SyncService.GOOGLE_DRIVE.value to stringResource(SYMR.strings.google_drive),
|
||||||
// KMK -->
|
// KMK -->
|
||||||
SyncManager.SyncService.WebDAV.value to stringResource(KMR.strings.web_dav),
|
SyncManager.SyncService.WEB_DAV.value to stringResource(KMR.strings.web_dav),
|
||||||
// KMK <--
|
// KMK <--
|
||||||
),
|
),
|
||||||
title = stringResource(SYMR.strings.pref_sync_service),
|
title = stringResource(SYMR.strings.pref_sync_service),
|
||||||
|
|
@ -606,7 +607,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||||
SyncManager.SyncService.SYNCYOMI -> getSelfHostPreferences(syncPreferences)
|
SyncManager.SyncService.SYNCYOMI -> getSelfHostPreferences(syncPreferences)
|
||||||
SyncManager.SyncService.GOOGLE_DRIVE -> getGoogleDrivePreferences()
|
SyncManager.SyncService.GOOGLE_DRIVE -> getGoogleDrivePreferences()
|
||||||
// KMK -->
|
// KMK -->
|
||||||
SyncManager.SyncService.WebDAV -> getWebDavPreferences(syncPreferences)
|
SyncManager.SyncService.WEB_DAV -> getWebDavPreferences(syncPreferences)
|
||||||
// KMK <--
|
// KMK <--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -759,7 +760,9 @@ object SettingsDataScreen : SearchableSettings {
|
||||||
title = stringResource(SYMR.strings.pref_sync_api_key),
|
title = stringResource(SYMR.strings.pref_sync_api_key),
|
||||||
subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ),
|
subtitle = stringResource(SYMR.strings.pref_sync_api_key_summ),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
|
scope.launch {
|
||||||
syncPreferences.clientAPIKey().set(it)
|
syncPreferences.clientAPIKey().set(it)
|
||||||
|
}
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
icon = null,
|
icon = null,
|
||||||
|
|
@ -808,17 +811,28 @@ object SettingsDataScreen : SearchableSettings {
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
run {
|
||||||
preference = syncPreferences.webDavPassword(),
|
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),
|
title = stringResource(KMR.strings.pref_webdav_password),
|
||||||
subtitle = stringResource(KMR.strings.pref_webdav_password_summ),
|
subtitle = stringResource(KMR.strings.pref_webdav_password_summ),
|
||||||
onValueChanged = { newValue ->
|
onClick = {
|
||||||
scope.launch {
|
dialogOpen = true
|
||||||
syncPreferences.webDavPassword().set(newValue)
|
},
|
||||||
}
|
)
|
||||||
true
|
|
||||||
},
|
},
|
||||||
),
|
|
||||||
Preference.PreferenceItem.EditTextPreference(
|
Preference.PreferenceItem.EditTextPreference(
|
||||||
preference = syncPreferences.webDavFolder(),
|
preference = syncPreferences.webDavFolder(),
|
||||||
title = stringResource(KMR.strings.pref_webdav_folder),
|
title = stringResource(KMR.strings.pref_webdav_folder),
|
||||||
|
|
@ -832,7 +846,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// MK <--
|
// KMK <--
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getSyncNowPref(): Preference.PreferenceGroup {
|
private fun getSyncNowPref(): Preference.PreferenceGroup {
|
||||||
|
|
|
||||||
|
|
@ -293,9 +293,14 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||||
title = stringResource(KMR.strings.pref_show_empty_categories_search),
|
title = stringResource(KMR.strings.pref_show_empty_categories_search),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
preference = libraryPreferences.syncOnAdd(),
|
preference = libraryPreferences.fetchMetadataOnAdd(),
|
||||||
title = stringResource(KMR.strings.pref_sync_manga_on_add),
|
title = stringResource(KMR.strings.pref_fetch_manga_metadata_on_add),
|
||||||
subtitle = stringResource(KMR.strings.pref_sync_manga_on_add_description),
|
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 <--
|
// KMK <--
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||||
enabled = passwordProtectDownloads,
|
enabled = passwordProtectDownloads,
|
||||||
|
|
||||||
),
|
),
|
||||||
kotlin.run {
|
run {
|
||||||
var dialogOpen by remember { mutableStateOf(false) }
|
var dialogOpen by remember { mutableStateOf(false) }
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
PasswordDialog(
|
PasswordDialog(
|
||||||
|
|
@ -183,7 +183,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||||
securityPreferences.cbzPassword().set("")
|
securityPreferences.cbzPassword().set("")
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
kotlin.run {
|
run {
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
val count by securityPreferences.authenticatorTimeRanges().collectAsState()
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
|
|
@ -199,7 +199,7 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
kotlin.run {
|
run {
|
||||||
val selection by securityPreferences.authenticatorDays().collectAsState()
|
val selection by securityPreferences.authenticatorDays().collectAsState()
|
||||||
var dialogOpen by remember { mutableStateOf(false) }
|
var dialogOpen by remember { mutableStateOf(false) }
|
||||||
if (dialogOpen) {
|
if (dialogOpen) {
|
||||||
|
|
@ -332,13 +332,16 @@ object SettingsSecurityScreen : SearchableSettings {
|
||||||
fun PasswordDialog(
|
fun PasswordDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onReturnPassword: (String) -> Unit,
|
onReturnPassword: (String) -> Unit,
|
||||||
|
// KMK -->
|
||||||
|
title: StringResource = SYMR.strings.cbz_archive_password,
|
||||||
|
// KMK <--
|
||||||
) {
|
) {
|
||||||
var password by rememberSaveable { mutableStateOf("") }
|
var password by rememberSaveable { mutableStateOf("") }
|
||||||
var passwordVisibility by remember { mutableStateOf(false) }
|
var passwordVisibility by remember { mutableStateOf(false) }
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
|
|
||||||
title = { Text(text = stringResource(SYMR.strings.cbz_archive_password)) },
|
title = { Text(text = stringResource(title)) },
|
||||||
text = {
|
text = {
|
||||||
TextField(
|
TextField(
|
||||||
value = password,
|
value = password,
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,9 @@ fun UpdateScreen(
|
||||||
else -> {
|
else -> {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
// KMK -->
|
||||||
|
val uiModels = remember(state.items) { state.getUiModel() }
|
||||||
|
// KMK <--
|
||||||
|
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = isRefreshing,
|
refreshing = isRefreshing,
|
||||||
|
|
@ -134,8 +137,8 @@ fun UpdateScreen(
|
||||||
updatesLastUpdatedItem(lastUpdated)
|
updatesLastUpdatedItem(lastUpdated)
|
||||||
|
|
||||||
updatesUiItems(
|
updatesUiItems(
|
||||||
uiModels = state.getUiModel(),
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
|
uiModels = uiModels,
|
||||||
expandedState = state.expandedState,
|
expandedState = state.expandedState,
|
||||||
collapseToggle = collapseToggle,
|
collapseToggle = collapseToggle,
|
||||||
usePanoramaCover = usePanoramaCover,
|
usePanoramaCover = usePanoramaCover,
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class SyncManager(
|
||||||
SYNCYOMI(1),
|
SYNCYOMI(1),
|
||||||
GOOGLE_DRIVE(2),
|
GOOGLE_DRIVE(2),
|
||||||
// KMK -->
|
// KMK -->
|
||||||
WebDAV(3),
|
WEB_DAV(3),
|
||||||
// KMK <--
|
// KMK <--
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -142,7 +142,7 @@ class SyncManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMK -->
|
// KMK -->
|
||||||
SyncService.WebDAV -> {
|
SyncService.WEB_DAV -> {
|
||||||
WebDavSyncService(context, json, syncPreferences, notifier)
|
WebDavSyncService(context, json, syncPreferences, notifier)
|
||||||
}
|
}
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ class WebDavSyncService(
|
||||||
private val url: String = syncPreferences.webDavUrl().get().trim()
|
private val url: String = syncPreferences.webDavUrl().get().trim()
|
||||||
private val folder: String = syncPreferences.webDavFolder().get().trim('/')
|
private val folder: String = syncPreferences.webDavFolder().get().trim('/')
|
||||||
private val username: String = syncPreferences.webDavUsername().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 val credentials: String = Credentials.basic(username, password)
|
||||||
|
|
||||||
private fun buildWebDavFileUrl(fileName: String = "backup.proto"): String {
|
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.MetadataSource
|
||||||
import eu.kanade.tachiyomi.source.online.NamespaceSource
|
import eu.kanade.tachiyomi.source.online.NamespaceSource
|
||||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import exh.metadata.metadata.NHentaiSearchMetadata
|
import exh.metadata.metadata.NHentaiSearchMetadata
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
import exh.metadata.metadata.base.RaisedTag
|
import exh.metadata.metadata.base.RaisedTag
|
||||||
|
|
@ -30,7 +29,7 @@ import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import tachiyomi.core.common.util.lang.withIOContext
|
||||||
|
|
||||||
class NHentai(delegate: HttpSource, val context: Context) :
|
class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
DelegatedHttpSource(delegate),
|
DelegatedHttpSource(delegate),
|
||||||
|
|
@ -72,12 +71,8 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) {
|
override suspend fun parseIntoMetadata(metadata: NHentaiSearchMetadata, input: Response) {
|
||||||
val body = input.body.string()
|
if (nhConfig == null) getNhConfig()
|
||||||
val server = MEDIA_SERVER_REGEX.find(body)?.groupValues?.get(1)?.toInt() ?: 1
|
val jsonResponse = jsonParser.decodeFromString<JsonResponse>(input.body.string())
|
||||||
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)
|
|
||||||
|
|
||||||
with(metadata) {
|
with(metadata) {
|
||||||
nhId = jsonResponse.id
|
nhId = jsonResponse.id
|
||||||
|
|
@ -88,8 +83,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
|
|
||||||
mediaId = jsonResponse.mediaId
|
mediaId = jsonResponse.mediaId
|
||||||
|
|
||||||
mediaServer = server
|
|
||||||
|
|
||||||
jsonResponse.title?.let { title ->
|
jsonResponse.title?.let { title ->
|
||||||
japaneseTitle = title.japanese
|
japaneseTitle = title.japanese
|
||||||
shortTitle = title.pretty
|
shortTitle = title.pretty
|
||||||
|
|
@ -98,16 +91,11 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
|
|
||||||
preferredTitle = this@NHentai.preferredTitle
|
preferredTitle = this@NHentai.preferredTitle
|
||||||
|
|
||||||
jsonResponse.images?.let { images ->
|
coverImageUrl =
|
||||||
coverImageType = input.asJsoup(body).parseCoverType()
|
jsonResponse.cover?.path?.let { "$thumbServer/$it" }
|
||||||
?: images.cover?.type
|
?: jsonResponse.thumbnail?.path?.let { "$thumbServer/$it" }
|
||||||
images.pages.mapNotNull {
|
|
||||||
it.type
|
pageImagePreviewUrls = jsonResponse.pages.mapNotNull { it.thumbnail }
|
||||||
}.let {
|
|
||||||
pageImageTypes = it
|
|
||||||
}
|
|
||||||
thumbnailImageType = images.thumbnail?.type
|
|
||||||
}
|
|
||||||
|
|
||||||
scanlator = jsonResponse.scanlator?.trimOrNull()
|
scanlator = jsonResponse.scanlator?.trimOrNull()
|
||||||
|
|
||||||
|
|
@ -128,17 +116,13 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// KMK -->
|
@Serializable
|
||||||
/**
|
data class JsonConfig(
|
||||||
* Site JSON is saying cover of type `w` but instead it's using cover like `cover.jpg.webp`
|
@SerialName("image_servers")
|
||||||
*/
|
val imageServers: List<String> = emptyList(),
|
||||||
private fun Document.parseCoverType(): String? {
|
@SerialName("thumb_servers")
|
||||||
return selectFirst("#cover > a > img")?.attr("data-src")
|
val thumbServers: List<String> = emptyList(),
|
||||||
?.substringAfterLast('/')
|
)
|
||||||
?.substringAfter('.')
|
|
||||||
?.first()?.toString()
|
|
||||||
}
|
|
||||||
// KMK <--
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class JsonResponse(
|
data class JsonResponse(
|
||||||
|
|
@ -146,7 +130,8 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
@SerialName("media_id")
|
@SerialName("media_id")
|
||||||
val mediaId: String? = null,
|
val mediaId: String? = null,
|
||||||
val title: JsonTitle? = null,
|
val title: JsonTitle? = null,
|
||||||
val images: JsonImages? = null,
|
val cover: JsonPage? = null,
|
||||||
|
val thumbnail: JsonPage? = null,
|
||||||
val scanlator: String? = null,
|
val scanlator: String? = null,
|
||||||
@SerialName("upload_date")
|
@SerialName("upload_date")
|
||||||
val uploadDate: Long? = null,
|
val uploadDate: Long? = null,
|
||||||
|
|
@ -155,6 +140,7 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
val numPages: Int? = null,
|
val numPages: Int? = null,
|
||||||
@SerialName("num_favorites")
|
@SerialName("num_favorites")
|
||||||
val numFavorites: Long? = null,
|
val numFavorites: Long? = null,
|
||||||
|
val pages: List<JsonPage> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -164,21 +150,12 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
val pretty: String? = null,
|
val pretty: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class JsonImages(
|
|
||||||
val pages: List<JsonPage> = emptyList(),
|
|
||||||
val cover: JsonPage? = null,
|
|
||||||
val thumbnail: JsonPage? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class JsonPage(
|
data class JsonPage(
|
||||||
@SerialName("t")
|
val path: String? = null,
|
||||||
val type: String? = null,
|
|
||||||
@SerialName("w")
|
|
||||||
val width: Long? = null,
|
val width: Long? = null,
|
||||||
@SerialName("h")
|
|
||||||
val height: Long? = null,
|
val height: Long? = null,
|
||||||
|
val thumbnail: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -203,20 +180,16 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getPagePreviewList(manga: SManga, chapters: List<SChapter>, page: Int): PagePreviewPage {
|
override suspend fun getPagePreviewList(manga: SManga, chapters: List<SChapter>, page: Int): PagePreviewPage {
|
||||||
|
if (nhConfig == null) getNhConfig()
|
||||||
val metadata = fetchOrLoadMetadata(manga.id()) {
|
val metadata = fetchOrLoadMetadata(manga.id()) {
|
||||||
client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
|
client.newCall(mangaDetailsRequest(manga)).awaitSuccess()
|
||||||
}
|
}
|
||||||
return PagePreviewPage(
|
return PagePreviewPage(
|
||||||
page,
|
page,
|
||||||
metadata.pageImageTypes.mapIndexed { index, s ->
|
metadata.pageImagePreviewUrls.mapIndexed { index, path ->
|
||||||
PagePreviewInfo(
|
PagePreviewInfo(
|
||||||
index + 1,
|
index + 1,
|
||||||
imageUrl = thumbnailUrlFromType(
|
imageUrl = "$thumbServer/$path",
|
||||||
metadata.mediaId!!,
|
|
||||||
metadata.mediaServer ?: 1,
|
|
||||||
index + 1,
|
|
||||||
s,
|
|
||||||
)!!,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
|
|
@ -224,14 +197,22 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun thumbnailUrlFromType(
|
var nhConfig: JsonConfig? = null
|
||||||
mediaId: String,
|
suspend fun getNhConfig() {
|
||||||
mediaServer: Int,
|
try {
|
||||||
page: Int,
|
val body = withIOContext { client.newCall(GET("https://nhentai.net/api/v2/config", headers)).awaitSuccess() }
|
||||||
t: String,
|
.use { it.body.string() }
|
||||||
) = NHentaiSearchMetadata.typeToExtension(t)?.let {
|
nhConfig = jsonParser.decodeFromString<JsonConfig>(body)
|
||||||
"https://t$mediaServer.nhentai.net/galleries/$mediaId/${page}t.$it"
|
} 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 {
|
override suspend fun fetchPreviewImage(page: PagePreviewInfo, cacheControl: CacheControl?): Response {
|
||||||
return client.newCachelessCallWithProgress(
|
return client.newCachelessCallWithProgress(
|
||||||
|
|
@ -248,10 +229,6 @@ class NHentai(delegate: HttpSource, val context: Context) :
|
||||||
private val jsonParser = Json {
|
private val jsonParser = Json {
|
||||||
ignoreUnknownKeys = true
|
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:"
|
private const val TITLE_PREF = "Display manga title as:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse
|
package eu.kanade.tachiyomi.ui.browse
|
||||||
|
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
|
|
@ -60,22 +59,20 @@ class BulkFavoriteScreenModel(
|
||||||
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
|
||||||
private val addTracks: AddTracks = Injekt.get(),
|
private val addTracks: AddTracks = Injekt.get(),
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
|
||||||
) : StateScreenModel<BulkFavoriteScreenModel.State>(initialState) {
|
) : StateScreenModel<BulkFavoriteScreenModel.State>(initialState) {
|
||||||
|
|
||||||
fun backHandler() {
|
fun backHandler() {
|
||||||
toggleSelectionMode()
|
toggleSelectionMode(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toggleSelectionMode(newMode: Boolean? = null) {
|
fun toggleSelectionMode(newMode: Boolean? = null) {
|
||||||
if (state.value.selectionMode) {
|
mutableState.update { state ->
|
||||||
clearSelection()
|
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) {
|
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) {
|
fun toggleSelection(manga: Manga, toSelectedState: Boolean? = null) {
|
||||||
mutableState.update { state ->
|
mutableState.update { state ->
|
||||||
|
|
@ -131,7 +128,7 @@ class BulkFavoriteScreenModel(
|
||||||
if (entryWithDuplicates != null) {
|
if (entryWithDuplicates != null) {
|
||||||
val (index, manga, duplicates) = entryWithDuplicates
|
val (index, manga, duplicates) = entryWithDuplicates
|
||||||
if (state.value.selection.size == 1) {
|
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))
|
setDialog(Dialog.AddDuplicateManga(manga, duplicates))
|
||||||
} else {
|
} else {
|
||||||
setDialog(Dialog.BulkAllowDuplicate(manga, duplicates, index))
|
setDialog(Dialog.BulkAllowDuplicate(manga, duplicates, index))
|
||||||
|
|
@ -154,7 +151,7 @@ class BulkFavoriteScreenModel(
|
||||||
val mangaList = if (skipAllDuplicates) getNotDuplicateLibraryMangas() else state.value.selection
|
val mangaList = if (skipAllDuplicates) getNotDuplicateLibraryMangas() else state.value.selection
|
||||||
if (mangaList.isEmpty()) {
|
if (mangaList.isEmpty()) {
|
||||||
stopRunning()
|
stopRunning()
|
||||||
toggleSelectionMode()
|
toggleSelectionMode(false)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val categories = getCategories()
|
val categories = getCategories()
|
||||||
|
|
@ -243,7 +240,7 @@ class BulkFavoriteScreenModel(
|
||||||
}
|
}
|
||||||
stopRunning()
|
stopRunning()
|
||||||
}
|
}
|
||||||
toggleSelectionMode(newMode = false)
|
toggleSelectionMode(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun moveMangaToCategoriesAndAddToLibrary(manga: Manga, categories: List<Long>) {
|
private fun moveMangaToCategoriesAndAddToLibrary(manga: Manga, categories: List<Long>) {
|
||||||
|
|
@ -256,17 +253,22 @@ class BulkFavoriteScreenModel(
|
||||||
setMangaDefaultChapterFlags.await(manga)
|
setMangaDefaultChapterFlags.await(manga)
|
||||||
addTracks.bindEnhancedTrackers(manga, source)
|
addTracks.bindEnhancedTrackers(manga, source)
|
||||||
updateManga.awaitUpdateFavorite(manga.id, true)
|
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 sManga = manga.toSManga()
|
||||||
val remoteManga = source.getMangaDetails(sManga)
|
if (fetchMetadataOnAdd) {
|
||||||
val chapters = source.getChapterList(sManga)
|
val remoteMetadata = source.getMangaDetails(sManga)
|
||||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
// 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)
|
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
logcat(LogPriority.ERROR, e)
|
||||||
snackbarHostState.showSnackbar(message = "Failed to sync manga: ${e.message}")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -351,18 +353,23 @@ class BulkFavoriteScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
updateManga.await(new.toMangaUpdate())
|
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 {
|
withIOContext {
|
||||||
try {
|
try {
|
||||||
val sManga = manga.toSManga()
|
val sManga = manga.toSManga()
|
||||||
val remoteManga = source.getMangaDetails(sManga)
|
if (fetchMetadataOnAdd) {
|
||||||
val chapters = source.getChapterList(sManga)
|
val remoteMetadata = source.getMangaDetails(sManga)
|
||||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
// 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)
|
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
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)
|
selectedMangaIds.addOrRemove(it.manga.id, selected)
|
||||||
it.copy(selected = selected)
|
it.copy(selected = selected)
|
||||||
}
|
}
|
||||||
state.copy(titleList = newItems.toImmutableList())
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
selectedPositions[1] = -1
|
selectedPositions[1] = -1
|
||||||
|
state.copy(titleList = newItems.toImmutableList())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invertSelection() {
|
fun invertSelection() {
|
||||||
|
|
@ -162,10 +161,10 @@ class MigrateMangaScreenModel(
|
||||||
selectedMangaIds.addOrRemove(it.manga.id, !it.selected)
|
selectedMangaIds.addOrRemove(it.manga.id, !it.selected)
|
||||||
it.copy(selected = !it.selected)
|
it.copy(selected = !it.selected)
|
||||||
}
|
}
|
||||||
state.copy(titleList = newItems.toImmutableList())
|
|
||||||
}
|
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
selectedPositions[1] = -1
|
selectedPositions[1] = -1
|
||||||
|
state.copy(titleList = newItems.toImmutableList())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.material3.SnackbarHostState
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -120,7 +119,6 @@ open class BrowseSourceScreenModel(
|
||||||
private val toggleIncognito: ToggleIncognito = Injekt.get(),
|
private val toggleIncognito: ToggleIncognito = Injekt.get(),
|
||||||
private val extensionManager: ExtensionManager = Injekt.get(),
|
private val extensionManager: ExtensionManager = Injekt.get(),
|
||||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
@ -424,20 +422,23 @@ open class BrowseSourceScreenModel(
|
||||||
|
|
||||||
updateManga.await(new.toMangaUpdate())
|
updateManga.await(new.toMangaUpdate())
|
||||||
// KMK -->
|
// KMK -->
|
||||||
if (new.favorite && libraryPreferences.syncOnAdd().get()) {
|
val fetchMetadataOnAdd = libraryPreferences.fetchMetadataOnAdd().get()
|
||||||
|
val fetchChaptersOnAdd = libraryPreferences.fetchChaptersOnAdd().get()
|
||||||
|
if (new.favorite && (fetchMetadataOnAdd || fetchChaptersOnAdd)) {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
try {
|
try {
|
||||||
val sManga = manga.toSManga()
|
val sManga = manga.toSManga()
|
||||||
val remoteManga = source.getMangaDetails(sManga)
|
if (fetchMetadataOnAdd) {
|
||||||
val chapters = source.getChapterList(sManga)
|
val remoteMetadata = source.getMangaDetails(sManga)
|
||||||
// Use `manga` instead of `new` so its title got updated with source's `getMangaDetails`
|
// 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)
|
syncChaptersWithSource.await(chapters, manga, source, false)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e)
|
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)
|
selectedErrorIds.addOrRemove(it.error.errorId, selected)
|
||||||
it.copy(selected = selected)
|
it.copy(selected = selected)
|
||||||
}
|
}
|
||||||
state.copy(items = newItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
selectedPositions[1] = -1
|
selectedPositions[1] = -1
|
||||||
|
state.copy(items = newItems)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invertSelection() {
|
fun invertSelection() {
|
||||||
|
|
@ -142,10 +141,10 @@ class LibraryUpdateErrorScreenModel(
|
||||||
selectedErrorIds.addOrRemove(it.error.errorId, !it.selected)
|
selectedErrorIds.addOrRemove(it.error.errorId, !it.selected)
|
||||||
it.copy(selected = !it.selected)
|
it.copy(selected = !it.selected)
|
||||||
}
|
}
|
||||||
state.copy(items = newItems)
|
|
||||||
}
|
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
selectedPositions[1] = -1
|
selectedPositions[1] = -1
|
||||||
|
state.copy(items = newItems)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
|
@ -189,11 +188,6 @@ data class LibraryUpdateErrorScreenState(
|
||||||
}
|
}
|
||||||
return uiModels
|
return uiModels
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getHeaderIndexes(): List<Int> = getUiModel()
|
|
||||||
.withIndex()
|
|
||||||
.filter { it.value is LibraryUpdateErrorUiModel.Header }
|
|
||||||
.map { it.index }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ import eu.kanade.tachiyomi.util.system.toast
|
||||||
import exh.debug.DebugToggles
|
import exh.debug.DebugToggles
|
||||||
import exh.eh.EHentaiUpdateHelper
|
import exh.eh.EHentaiUpdateHelper
|
||||||
import exh.log.xLogD
|
import exh.log.xLogD
|
||||||
|
import exh.log.xLogE
|
||||||
import exh.md.utils.FollowStatus
|
import exh.md.utils.FollowStatus
|
||||||
import exh.metadata.metadata.RaisedSearchMetadata
|
import exh.metadata.metadata.RaisedSearchMetadata
|
||||||
import exh.metadata.metadata.base.FlatMetadata
|
import exh.metadata.metadata.base.FlatMetadata
|
||||||
|
|
@ -1851,7 +1852,7 @@ class MangaScreenModel(
|
||||||
}
|
}
|
||||||
// KMK: auto track MangaDex
|
// KMK: auto track MangaDex
|
||||||
mdTrack.id !in tracks.map { it.trackerId } -> {
|
mdTrack.id !in tracks.map { it.trackerId } -> {
|
||||||
tracks + createMdListTrack()
|
createMdListTrack()?.let { tracks + it } ?: tracks
|
||||||
}
|
}
|
||||||
else -> tracks
|
else -> tracks
|
||||||
}
|
}
|
||||||
|
|
@ -1880,13 +1881,15 @@ class MangaScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
private suspend fun createMdListTrack(): Track {
|
private suspend fun createMdListTrack(): Track? {
|
||||||
|
try {
|
||||||
val state = successState!!
|
val state = successState!!
|
||||||
val mdManga = state.manga.takeIf { it.source in mangaDexSourceIds }
|
val mdManga = state.manga.takeIf { it.source in mangaDexSourceIds }
|
||||||
?: state.mergedData?.manga?.values?.find { 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)
|
val track = trackerManager.mdList.createInitialTracker(state.manga, mdManga)
|
||||||
.toDomainTrack(false)!!
|
.toDomainTrack(false)
|
||||||
|
?: throw IllegalStateException("Could not create initial track")
|
||||||
insertTrack.await(track)
|
insertTrack.await(track)
|
||||||
/* KMK -->
|
/* KMK -->
|
||||||
return TrackItem(
|
return TrackItem(
|
||||||
|
|
@ -1895,6 +1898,10 @@ class MangaScreenModel(
|
||||||
)
|
)
|
||||||
KMK <-- */
|
KMK <-- */
|
||||||
return getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id }
|
return getTracks.await(mangaId).first { it.trackerId == trackerManager.mdList.id }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
xLogE("Failed to create MangaDex track", e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -461,11 +461,12 @@ class UpdatesScreenModel(
|
||||||
selectedChapterIds.addOrRemove(it.update.chapterId, selected)
|
selectedChapterIds.addOrRemove(it.update.chapterId, selected)
|
||||||
it.copy(selected = selected)
|
it.copy(selected = selected)
|
||||||
}
|
}
|
||||||
state.copy(items = newItems.toPersistentList())
|
// KMK -->
|
||||||
}
|
|
||||||
|
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
selectedPositions[1] = -1
|
selectedPositions[1] = -1
|
||||||
|
// KMK <--
|
||||||
|
state.copy(items = newItems.toPersistentList())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invertSelection() {
|
fun invertSelection() {
|
||||||
|
|
@ -474,10 +475,12 @@ class UpdatesScreenModel(
|
||||||
selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected)
|
selectedChapterIds.addOrRemove(it.update.chapterId, !it.selected)
|
||||||
it.copy(selected = !it.selected)
|
it.copy(selected = !it.selected)
|
||||||
}
|
}
|
||||||
state.copy(items = newItems.toPersistentList())
|
// KMK -->
|
||||||
}
|
|
||||||
selectedPositions[0] = -1
|
selectedPositions[0] = -1
|
||||||
selectedPositions[1] = -1
|
selectedPositions[1] = -1
|
||||||
|
// KMK <--
|
||||||
|
state.copy(items = newItems.toPersistentList())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDialog(dialog: Dialog?) {
|
fun setDialog(dialog: Dialog?) {
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ class BrowseRecommendsScreen(
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
// KMK -->
|
// KMK -->
|
||||||
toggleSelectionMode = {
|
toggleSelectionMode = {
|
||||||
bulkFavoriteScreenModel.toggleSelectionMode()
|
bulkFavoriteScreenModel.toggleSelectionMode(true)
|
||||||
}.takeIf { !isExternalSource },
|
}.takeIf { !isExternalSource },
|
||||||
isRunning = bulkFavoriteState.isRunning,
|
isRunning = bulkFavoriteState.isRunning,
|
||||||
// KMK <--
|
// KMK <--
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,8 @@ fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) {
|
||||||
|
|
||||||
binding.pages.text = context.pluralStringResource(
|
binding.pages.text = context.pluralStringResource(
|
||||||
SYMR.plurals.num_pages,
|
SYMR.plurals.num_pages,
|
||||||
meta.pageImageTypes.size,
|
meta.pageImagePreviewUrls.size,
|
||||||
meta.pageImageTypes.size,
|
meta.pageImagePreviewUrls.size,
|
||||||
)
|
)
|
||||||
// KMK -->
|
// KMK -->
|
||||||
binding.pages.bindDrawable(context, R.drawable.ic_baseline_menu_book_24, iconColor, 4.dpToPx)
|
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_manga_id_index ON chapters(manga_id);
|
||||||
CREATE INDEX chapters_unread_by_manga_index ON chapters(manga_id, read) WHERE read = 0;
|
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
|
CREATE TRIGGER update_last_modified_at_chapters
|
||||||
AFTER UPDATE ON chapters
|
AFTER UPDATE ON chapters
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ CREATE TABLE excluded_scanlators(
|
||||||
ON DELETE CASCADE
|
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:
|
||||||
INSERT INTO excluded_scanlators(manga_id, scanlator)
|
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 history_history_chapter_id_index ON history(chapter_id);
|
||||||
|
CREATE INDEX idx_history_last_read ON history(last_read) WHERE last_read > 0;
|
||||||
|
|
||||||
getHistoryByMangaId:
|
getHistoryByMangaId:
|
||||||
SELECT
|
SELECT
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ CREATE TABLE mangas(
|
||||||
|
|
||||||
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
|
CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
|
||||||
CREATE INDEX mangas_url_index ON mangas(url);
|
CREATE INDEX mangas_url_index ON mangas(url);
|
||||||
|
CREATE INDEX idx_mangas_source ON mangas(source);
|
||||||
|
|
||||||
CREATE TRIGGER update_last_favorited_at_mangas
|
CREATE TRIGGER update_last_favorited_at_mangas
|
||||||
AFTER UPDATE OF favorite ON mangas
|
AFTER UPDATE OF favorite ON mangas
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ CREATE TABLE mangas_categories(
|
||||||
ON DELETE CASCADE
|
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
|
CREATE TRIGGER insert_manga_category_update_version AFTER INSERT ON mangas_categories
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE mangas
|
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)
|
fun autoUpdateMetadata() = preferenceStore.getBoolean("auto_update_metadata", false)
|
||||||
|
|
||||||
// KMK -->
|
// 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 <--
|
// KMK <--
|
||||||
|
|
||||||
fun showContinueReadingButton() = preferenceStore.getBoolean(
|
fun showContinueReadingButton() = preferenceStore.getBoolean(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="action_source_search">البحث عن مصدر</string>
|
<string name="action_source_search">البحث داخل المصدر</string>
|
||||||
<string name="action_toggle_nsfw_only">تبديل NSFW فقط</string>
|
<string name="action_toggle_nsfw_only">تبديل NSFW فقط</string>
|
||||||
<string name="action_bulk_select">وضع التحديد بالجملة</string>
|
<string name="action_bulk_select">وضع التحديد بالجملة</string>
|
||||||
<string name="action_faq_and_guides">الأسئلة الشائعة والأدلة</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_6hour">6 ساعات</string>
|
||||||
<string name="download_cache_renew_interval_12hour">12 ساعة</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_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">تنظيف التنزيلات غير صالحة</string>
|
||||||
<string name="pref_clean_invalid_downloads_summary">العثور على جميع التنزيلات والملفات والمجلدات التي لم يتم حفظها في مكتبتك وإزالتها</string>
|
<string name="pref_clean_invalid_downloads_summary">العثور على جميع التنزيلات والملفات والمجلدات التي لم يتم حفظها في مكتبتك وإزالتها</string>
|
||||||
<string name="invalid_downloads_cleaned">تم تنظيف التنزيلات غير صالحة</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_second_page">نسخ الصفحة الثانية إلى الحافظة</string>
|
||||||
<string name="action_copy_to_clipboard_combined_page">نسخ الصفحة المدمجة إلى الحافظة</string>
|
<string name="action_copy_to_clipboard_combined_page">نسخ الصفحة المدمجة إلى الحافظة</string>
|
||||||
<string name="auto_update_app_disable_auto_download">عدم التحميل التلقائي للتحديث</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="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>
|
</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="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="updating">Updating</string>
|
||||||
<string name="pref_show_empty_categories_search">Show empty categories during search/filtering</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_fetch_manga_metadata_on_add">Fetch metadata 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_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 -->
|
<!-- Extension section -->
|
||||||
<string name="extensions_page_need_refresh">Refresh extension page to update list.</string>
|
<string name="extensions_page_need_refresh">Refresh extension page to update list.</string>
|
||||||
<string name="extensions_page_more">More extensions...</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_unfinished_chapter">Di natapos na kabanata</string>
|
||||||
<string name="action_filter_non_library_entries">Mga entry na wala sa aklatan</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="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">Server URL ng WebDAV</string>
|
||||||
<string name="pref_webdav_url_summ">Ipasok ang address ng WebDAV server</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_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_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_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_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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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_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="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">Intervallo di rinnovo download cache</string>
|
||||||
<string name="download_cache_renew_interval_manual">Manuale</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_6hour">6 ore</string>
|
||||||
<string name="download_cache_renew_interval_12hour">12 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_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">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="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="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="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="whats_coming">In arrivo (a breve)</string>
|
||||||
<string name="action_allow_duplicate_manga">Consenti di farlo</string>
|
<string name="action_allow_duplicate_manga">Consenti</string>
|
||||||
<string name="action_skip_duplicate_manga">Salta esso</string>
|
<string name="action_skip_duplicate_manga">Salta</string>
|
||||||
<string name="action_allow_all_duplicate_manga">Consenti tutto</string>
|
<string name="action_allow_all_duplicate_manga">Consenti tutto</string>
|
||||||
<string name="action_skip_all_duplicate_manga">Salta 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_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="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="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="current_">Attuale: %1$s</string>
|
||||||
<string name="pref_theme_cover_based">Tema basato sulla copertina</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_tonalspot">Punto Tonale</string>
|
||||||
<string name="pref_theme_cover_based_style_neutral">Neutro</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_expressive">Espressivo</string>
|
||||||
<string name="pref_theme_cover_based_style_rainbow">Arcobaleno</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_fruitsalad">Insalata Di Frutta</string>
|
||||||
<string name="pref_theme_cover_based_style_monochrome">Monocromatico</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_fidelity">Fedeltà</string>
|
||||||
<string name="pref_theme_cover_based_style_content">Contenuto</string>
|
<string name="pref_theme_cover_based_style_content">Contenuto</string>
|
||||||
<string name="action_search_for_source">Cerca la sorgente</string>
|
<string name="action_search_for_source">Cerca fonte</string>
|
||||||
<string name="action_library_search">Ricerca libreria</string>
|
<string name="action_library_search">Cerca nella libreria</string>
|
||||||
<string name="theme_cloudflare">Cloudflare</string>
|
<string name="theme_cloudflare">Cloudflare</string>
|
||||||
<string name="theme_cottoncandy">Zucchero filato</string>
|
<string name="theme_cottoncandy">Zucchero filato</string>
|
||||||
<string name="theme_matrix">Matrice</string>
|
<string name="theme_matrix">Matrice</string>
|
||||||
|
|
@ -55,8 +55,8 @@
|
||||||
<string name="action_move_up">Sposta in alto</string>
|
<string name="action_move_up">Sposta in alto</string>
|
||||||
<string name="action_move_down">Sposta in basso</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="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_add_feed">Ricerche salvate (Tieni premuto per aggiungere al feed)</string>
|
||||||
<string name="saved_searches_delete">Ricerche salvate (Si prega di eliminare)</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_source_badge">Icona sorgente</string>
|
||||||
<string name="action_display_language_icon">Usa icona lingua</string>
|
<string name="action_display_language_icon">Usa icona lingua</string>
|
||||||
<string name="action_confirm_color">Conferma Colore</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="sort_feed_confirmation">Vuoi ordinare i feed in modo alfabetico?</string>
|
||||||
<string name="pref_custom_theme_style">Stile tema personalizzato</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="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_casual">Casuale</string>
|
||||||
<string name="custom_theme_palette_sunset">Tramonto</string>
|
<string name="custom_theme_palette_sunset">Tramonto</string>
|
||||||
<string name="custom_theme_palette_raspberry">Lampone</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_outrun">Superare</string>
|
||||||
<string name="custom_theme_palette_bubblegum">Gomma da masticare</string>
|
<string name="custom_theme_palette_bubblegum">Gomma da masticare</string>
|
||||||
<string name="custom_theme_palette_springfield">Springfield</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,58 @@
|
||||||
<string name="theme_matrix">매트릭스</string>
|
<string name="theme_matrix">매트릭스</string>
|
||||||
<string name="theme_mocha">모카</string>
|
<string name="theme_mocha">모카</string>
|
||||||
<string name="theme_sapphire">사파이어</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,10 @@
|
||||||
<string name="action_filter_unfinished_manga">Незаконченные тайтлы</string>
|
<string name="action_filter_unfinished_manga">Незаконченные тайтлы</string>
|
||||||
<string name="action_filter_unfinished_chapter">Незаконченные главы</string>
|
<string name="action_filter_unfinished_chapter">Незаконченные главы</string>
|
||||||
<string name="action_filter_non_library_entries">Тайтлы не в библиотеке</string>
|
<string name="action_filter_non_library_entries">Тайтлы не в библиотеке</string>
|
||||||
<string name="pref_sync_manga_on_add">Синхронизировать тайтлы при добавлении в библиотеку</string>
|
<string name="migrationConfigScreen_smartSearchSingleEntryTitle">Использовать умный поиск</string>
|
||||||
<string name="pref_sync_manga_on_add_description">Автоматически загружает метаданные и список глав при добавлении в библиотеку</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,19 @@
|
||||||
<string name="action_filter_unfinished_manga">尚未讀完</string>
|
<string name="action_filter_unfinished_manga">尚未讀完</string>
|
||||||
<string name="action_filter_unfinished_chapter">未讀完章節</string>
|
<string name="action_filter_unfinished_chapter">未讀完章節</string>
|
||||||
<string name="action_filter_non_library_entries">未收藏項目</string>
|
<string name="action_filter_non_library_entries">未收藏項目</string>
|
||||||
<string name="pref_sync_manga_on_add">新增至書庫時自動同步</string>
|
<string name="web_dav">WebDAV</string>
|
||||||
<string name="pref_sync_manga_on_add_description">將新漫畫加入書庫時,自動取得其元資料及章節列表</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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,12 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
||||||
|
|
||||||
var mediaId: String? = null
|
var mediaId: String? = null
|
||||||
|
|
||||||
var mediaServer: Int? = null
|
|
||||||
|
|
||||||
var japaneseTitle by titleDelegate(TITLE_TYPE_JAPANESE)
|
var japaneseTitle by titleDelegate(TITLE_TYPE_JAPANESE)
|
||||||
var englishTitle by titleDelegate(TITLE_TYPE_ENGLISH)
|
var englishTitle by titleDelegate(TITLE_TYPE_ENGLISH)
|
||||||
var shortTitle by titleDelegate(TITLE_TYPE_SHORT)
|
var shortTitle by titleDelegate(TITLE_TYPE_SHORT)
|
||||||
|
|
||||||
var coverImageType: String? = null
|
var coverImageUrl: String? = null
|
||||||
var pageImageTypes: List<String> = emptyList()
|
var pageImagePreviewUrls: List<String> = emptyList()
|
||||||
var thumbnailImageType: String? = null
|
|
||||||
|
|
||||||
var scanlator: String? = null
|
var scanlator: String? = null
|
||||||
|
|
||||||
|
|
@ -46,16 +43,6 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
||||||
override fun createMangaInfo(manga: SManga): SManga {
|
override fun createMangaInfo(manga: SManga): SManga {
|
||||||
val key = nhId?.let { nhIdToPath(it) }
|
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) {
|
val title = when (preferredTitle) {
|
||||||
TITLE_TYPE_SHORT -> shortTitle ?: englishTitle ?: japaneseTitle ?: manga.title
|
TITLE_TYPE_SHORT -> shortTitle ?: englishTitle ?: japaneseTitle ?: manga.title
|
||||||
0, TITLE_TYPE_ENGLISH -> englishTitle ?: japaneseTitle ?: shortTitle ?: manga.title
|
0, TITLE_TYPE_ENGLISH -> englishTitle ?: japaneseTitle ?: shortTitle ?: manga.title
|
||||||
|
|
@ -88,7 +75,7 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
||||||
|
|
||||||
return manga.copy(
|
return manga.copy(
|
||||||
url = key ?: manga.url,
|
url = key ?: manga.url,
|
||||||
thumbnail_url = cover ?: manga.thumbnail_url,
|
thumbnail_url = coverImageUrl ?: manga.thumbnail_url,
|
||||||
title = title,
|
title = title,
|
||||||
artist = group ?: manga.artist,
|
artist = group ?: manga.artist,
|
||||||
author = artist ?: manga.artist,
|
author = artist ?: manga.artist,
|
||||||
|
|
@ -116,9 +103,8 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
||||||
getItem(japaneseTitle) { stringResource(SYMR.strings.japanese_title) },
|
getItem(japaneseTitle) { stringResource(SYMR.strings.japanese_title) },
|
||||||
getItem(englishTitle) { stringResource(SYMR.strings.english_title) },
|
getItem(englishTitle) { stringResource(SYMR.strings.english_title) },
|
||||||
getItem(shortTitle) { stringResource(SYMR.strings.short_title) },
|
getItem(shortTitle) { stringResource(SYMR.strings.short_title) },
|
||||||
getItem(coverImageType) { stringResource(SYMR.strings.cover_image_file_type) },
|
getItem(coverImageUrl) { stringResource(SYMR.strings.thumbnail_url) },
|
||||||
getItem(pageImageTypes.size) { stringResource(SYMR.strings.page_count) },
|
getItem(pageImagePreviewUrls.size) { stringResource(SYMR.strings.page_count) },
|
||||||
getItem(thumbnailImageType) { stringResource(SYMR.strings.thumbnail_image_file_type) },
|
|
||||||
getItem(scanlator) { stringResource(MR.strings.scanlator) },
|
getItem(scanlator) { stringResource(MR.strings.scanlator) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -137,15 +123,6 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
||||||
private const val NHENTAI_GROUP_NAMESPACE = "group"
|
private const val NHENTAI_GROUP_NAMESPACE = "group"
|
||||||
const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
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) =
|
fun nhUrlToId(url: String) =
|
||||||
url.split("/").last { it.isNotBlank() }.toLong()
|
url.split("/").last { it.isNotBlank() }.toLong()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ import java.nio.charset.StandardCharsets
|
||||||
import kotlin.time.Duration.Companion.days
|
import kotlin.time.Duration.Companion.days
|
||||||
import tachiyomi.domain.source.model.Source as DomainSource
|
import tachiyomi.domain.source.model.Source as DomainSource
|
||||||
|
|
||||||
actual class LocalSource(
|
class LocalSource(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val fileSystem: LocalSourceFileSystem,
|
private val fileSystem: LocalSourceFileSystem,
|
||||||
private val coverManager: LocalCoverManager,
|
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